Enable SLURM allocation synchronization action.

--- app/scripts/components/slurm/actions.js
+++ app/scripts/components/slurm/actions.js
@@ -3,11 +3,15 @@
   ActionConfigurationProvider.register('SLURM.Allocation', {
     order: [
       'details',
+      'pull',
       'edit',
       'cancel',
       'destroy'
     ],
     options: {
+      pull: {
+        title: gettext('Synchronise')
+      },
       details: {
         title: gettext('Details'),
         component: 'slurmAllocationDetailsDialog',

<|original_code|>
// @ngInject
export default function actionConfig(ActionConfigurationProvider, DEFAULT_EDIT_ACTION) {
  ActionConfigurationProvider.register('SLURM.Allocation', {
    order: [
      'details',
      'edit',
      'cancel',
      'destroy'
    ],
    options: {
      details: {
        title: gettext('Details'),
        component: 'slurmAllocationDetailsDialog',
        enabled: true,
        useResolve: true,
        type: 'form',
        dialogSize: 'lg',
      },
      edit: angular.merge({}, DEFAULT_EDIT_ACTION, {
        successMessage: gettext('Allocation has been updated.'),
        fields: {
          cpu_limit: {
            type: 'integer',
            label: gettext('CPU limit, minutes'),
            required: true,
            resource_default_value: true,
          },
          gpu_limit: {
            type: 'integer',
            label: gettext('GPU limit, minutes'),
            required: true,
            resource_default_value: true,
          },
          ram_limit: {
            type: 'integer',
            label: gettext('RAM limit, MB'),
            required: true,
            resource_default_value: true,
          }
        }
      }),
    }
  });
}

<|edits_diff|>
--- app/scripts/components/slurm/actions.js
+++ app/scripts/components/slurm/actions.js
@@ -3,6 +3,7 @@
   ActionConfigurationProvider.register('SLURM.Allocation', {
     order: [
       'details',
+      'pull',
       'edit',
       'cancel',
       'destroy'
<|current_version|>
// @ngInject
export default function actionConfig(ActionConfigurationProvider, DEFAULT_EDIT_ACTION) {
  ActionConfigurationProvider.register('SLURM.Allocation', {
    order: [
      'details',
      'pull',
      'edit',
      'cancel',
      'destroy'
    ],
    options: {
      details: {
        title: gettext('Details'),
        component: 'slurmAllocationDetailsDialog',
        enabled: true,
        useResolve: true,
        type: 'form',
        dialogSize: 'lg',
      },
      edit: angular.merge({}, DEFAULT_EDIT_ACTION, {
        successMessage: gettext('Allocation has been updated.'),
        fields: {
          cpu_limit: {
            type: 'integer',
            label: gettext('CPU limit, minutes'),
            required: true,
            resource_default_value: true,
          },
          gpu_limit: {
            type: 'integer',
            label: gettext('GPU limit, minutes'),
            required: true,
            resource_default_value: true,
          },
          ram_limit: {
            type: 'integer',
            label: gettext('RAM limit, MB'),
            required: true,
            resource_default_value: true,
          }
        }
      }),
    }
  });
}

<|next_version|>
// @ngInject
export default function actionConfig(ActionConfigurationProvider, DEFAULT_EDIT_ACTION) {
  ActionConfigurationProvider.register('SLURM.Allocation', {
    order: [
      'details',
      'pull',
      'edit',
      'cancel',
      'destroy'
    ],
    options: {
      pull: {
        title: gettext('Synchronise')
      },
      details: {
        title: gettext('Details'),
        component: 'slurmAllocationDetailsDialog',
        enabled: true,
        useResolve: true,
        type: 'form',
        dialogSize: 'lg',
      },
      edit: angular.merge({}, DEFAULT_EDIT_ACTION, {
        successMessage: gettext('Allocation has been updated.'),
        fields: {
          cpu_limit: {
            type: 'integer',
            label: gettext('CPU limit, minutes'),
            required: true,
            resource_default_value: true,
          },
          gpu_limit: {
            type: 'integer',
            label: gettext('GPU limit, minutes'),
            required: true,
            resource_default_value: true,
          },
          ram_limit: {
            type: 'integer',
            label: gettext('RAM limit, MB'),
            required: true,
            resource_default_value: true,
          }
        }
      }),
    }
  });
}


describe('Creating custom map controls', function () {
  var map;

  beforeEach(function() {
    map = map || new GMaps({
      el : '#basic-map',
      lat: -12.0433,
      lng: -77.0283,
      zoom: 12
    });
  });

  it('should add default styles for the control', function () {
    map.addControl({
      position: 'top_right',
      content: 'Geolocate'
    });

    expect(map.controls[0].position).toEqual(google.maps.ControlPosition.TOP_RIGHT);
    expect(map.controls[0].style.fontFamily).not.toEqual('');
  });

  it('should leave off default styles if requested', function () {
    map.addControl({
      position: 'top_right',
      disableDefaultStyles: true,
      content: '<i class="icon"></i>'
    });

    expect(map.controls[1].position).toEqual(google.maps.ControlPosition.TOP_RIGHT);
    expect(map.controls[1].style.fontFamily).toEqual('');
  });

  it('should remove control', function () {
    var control = map.controls[0];
    map.removeControl(control);

    expect(map.controls.length).toEqual(1);
    expect(map.controls[0]).not.toEqual(control);
  });
});

describe('Creating custom map controls', function () {
  var map;

  beforeEach(function() {
    map = map || new GMaps({
      el : '#basic-map',
      lat: -12.0433,
      lng: -77.0283,
      zoom: 12
    });
  });

  it('should add default styles for the control', function () {
    map.addControl({
      position: 'top_right',
      content: 'Geolocate'
    });

    expect(map.controls[0].position).toEqual(google.maps.ControlPosition.TOP_RIGHT);
    expect(map.controls[0].style.fontFamily).not.toEqual('');
  });

  it('should leave off default styles if requested', function () {
    map.addControl({
      position: 'top_right',
      disableDefaultStyles: true,
      content: '<i class="icon"></i>'
    });

    expect(map.controls[1].position).toEqual(google.maps.ControlPosition.TOP_RIGHT);
    expect(map.map.controls[google.maps.ControlPosition.TOP_RIGHT].length).toEqual(2);
    expect(map.controls[1].style.fontFamily).toEqual('');
  });

  it('should remove control', function () {
    var control = map.controls[0];
    map.removeControl(control);

    expect(map.controls.length).toEqual(1);
    expect(map.map.controls[google.maps.ControlPosition.TOP_RIGHT].length).toEqual(1);
    expect(map.controls[0]).not.toEqual(control);
  });
});

Add unit test for the internal state after removing controls

--- test/spec/ControlSpec.js
+++ test/spec/ControlSpec.js
@@ -28,6 +28,7 @@
     });

     expect(map.controls[1].position).toEqual(google.maps.ControlPosition.TOP_RIGHT);
+    expect(map.map.controls[google.maps.ControlPosition.TOP_RIGHT].length).toEqual(2);
     expect(map.controls[1].style.fontFamily).toEqual('');
   });

@@ -36,6 +37,7 @@
     map.removeControl(control);

     expect(map.controls.length).toEqual(1);
+    expect(map.map.controls[google.maps.ControlPosition.TOP_RIGHT].length).toEqual(1);
     expect(map.controls[0]).not.toEqual(control);
   });
 });

describe('Creating custom map controls', function () {
  var map;

  beforeEach(function() {
    map = map || new GMaps({
      el : '#basic-map',
      lat: -12.0433,
      lng: -77.0283,
      zoom: 12
    });
  });

  it('should add default styles for the control', function () {
    map.addControl({
      position: 'top_right',
      content: 'Geolocate'
    });

    expect(map.controls[0].position).toEqual(google.maps.ControlPosition.TOP_RIGHT);
    expect(map.controls[0].style.fontFamily).not.toEqual('');
  });

  it('should leave off default styles if requested', function () {
    map.addControl({
      position: 'top_right',
      disableDefaultStyles: true,
      content: '<i class="icon"></i>'
    });

    expect(map.controls[1].position).toEqual(google.maps.ControlPosition.TOP_RIGHT);
    expect(map.map.controls[google.maps.ControlPosition.TOP_RIGHT].length).toEqual(2);
    expect(map.controls[1].style.fontFamily).toEqual('');
  });

  it('should remove control', function () {
    var control = map.controls[0];
    map.removeControl(control);

    expect(map.controls.length).toEqual(1);
    expect(map.controls[0]).not.toEqual(control);
  });
});

<|original_code|>
describe('Creating custom map controls', function () {
  var map;

  beforeEach(function() {
    map = map || new GMaps({
      el : '#basic-map',
      lat: -12.0433,
      lng: -77.0283,
      zoom: 12
    });
  });

  it('should add default styles for the control', function () {
    map.addControl({
      position: 'top_right',
      content: 'Geolocate'
    });

    expect(map.controls[0].position).toEqual(google.maps.ControlPosition.TOP_RIGHT);
    expect(map.controls[0].style.fontFamily).not.toEqual('');
  });

  it('should leave off default styles if requested', function () {
    map.addControl({
      position: 'top_right',
      disableDefaultStyles: true,
      content: ''
    });

    expect(map.controls[1].position).toEqual(google.maps.ControlPosition.TOP_RIGHT);
    expect(map.controls[1].style.fontFamily).toEqual('');
  });

  it('should remove control', function () {
    var control = map.controls[0];
    map.removeControl(control);

    expect(map.controls.length).toEqual(1);
    expect(map.controls[0]).not.toEqual(control);
  });
});
<|edits_diff|>
--- test/spec/ControlSpec.js
+++ test/spec/ControlSpec.js
@@ -28,6 +28,7 @@
     });

     expect(map.controls[1].position).toEqual(google.maps.ControlPosition.TOP_RIGHT);
+    expect(map.map.controls[google.maps.ControlPosition.TOP_RIGHT].length).toEqual(2);
     expect(map.controls[1].style.fontFamily).toEqual('');
   });

<|current_version|>
describe('Creating custom map controls', function () {
  var map;

  beforeEach(function() {
    map = map || new GMaps({
      el : '#basic-map',
      lat: -12.0433,
      lng: -77.0283,
      zoom: 12
    });
  });

  it('should add default styles for the control', function () {
    map.addControl({
      position: 'top_right',
      content: 'Geolocate'
    });

    expect(map.controls[0].position).toEqual(google.maps.ControlPosition.TOP_RIGHT);
    expect(map.controls[0].style.fontFamily).not.toEqual('');
  });

  it('should leave off default styles if requested', function () {
    map.addControl({
      position: 'top_right',
      disableDefaultStyles: true,
      content: ''
    });

    expect(map.controls[1].position).toEqual(google.maps.ControlPosition.TOP_RIGHT);
    expect(map.map.controls[google.maps.ControlPosition.TOP_RIGHT].length).toEqual(2);
    expect(map.controls[1].style.fontFamily).toEqual('');
  });

  it('should remove control', function () {
    var control = map.controls[0];
    map.removeControl(control);

    expect(map.controls.length).toEqual(1);
    expect(map.controls[0]).not.toEqual(control);
  });
});
<|next_version|>
describe('Creating custom map controls', function () {
  var map;

  beforeEach(function() {
    map = map || new GMaps({
      el : '#basic-map',
      lat: -12.0433,
      lng: -77.0283,
      zoom: 12
    });
  });

  it('should add default styles for the control', function () {
    map.addControl({
      position: 'top_right',
      content: 'Geolocate'
    });

    expect(map.controls[0].position).toEqual(google.maps.ControlPosition.TOP_RIGHT);
    expect(map.controls[0].style.fontFamily).not.toEqual('');
  });

  it('should leave off default styles if requested', function () {
    map.addControl({
      position: 'top_right',
      disableDefaultStyles: true,
      content: ''
    });

    expect(map.controls[1].position).toEqual(google.maps.ControlPosition.TOP_RIGHT);
    expect(map.map.controls[google.maps.ControlPosition.TOP_RIGHT].length).toEqual(2);
    expect(map.controls[1].style.fontFamily).toEqual('');
  });

  it('should remove control', function () {
    var control = map.controls[0];
    map.removeControl(control);

    expect(map.controls.length).toEqual(1);
    expect(map.map.controls[google.maps.ControlPosition.TOP_RIGHT].length).toEqual(1);
    expect(map.controls[0]).not.toEqual(control);
  });
});


Store date when viewing a month

Store date when viewing a month

--- app/assets/javascripts/calendar.js
+++ app/assets/javascripts/calendar.js
@@ -4,6 +4,7 @@
   if (!calendar_container) { return }

   calendar = new FullCalendar.Calendar(calendar_container, {
+    'initialDate': sessionStorage.getItem('lastDate') || null,
     'events': 'assignments.json',
     'startParam': 'start_date',
     'endParam': 'end_date',
@@ -15,8 +16,13 @@
         $('td[data-date=' + dateString + ']').removeClass('day-empty');
         date.setDate(date.getDate() + 1);
       }
+    },
+    'datesSet': function(info) {
+      var currentStart = info.view.currentStart.toISOString();
+      sessionStorage.setItem('lastDate', currentStart);
     }
   });
+
   calendar.render();

   $('#calendar').on('click', 'td.day-empty', function() {

<|original_code|>
var calendar;
$(document).ready(function() {
  var calendar_container = document.getElementById('calendar');
  if (!calendar_container) { return }

  calendar = new FullCalendar.Calendar(calendar_container, {
    'events': 'assignments.json',
    'startParam': 'start_date',
    'endParam': 'end_date',
    'dayCellClassNames': 'day-empty',
    'eventDidMount': function(info) {
      var date = info.event.start;
      while (date < info.event.end) {
        var dateString = date.toISOString().split('T')[0];
        $('td[data-date=' + dateString + ']').removeClass('day-empty');
        date.setDate(date.getDate() + 1);
      }
    }
  });
  calendar.render();

  $('#calendar').on('click', 'td.day-empty', function() {
    window.location = 'assignments/new?date=' + $(this).data('date');
  });
});

<|edits_diff|>
--- app/assets/javascripts/calendar.js
+++ app/assets/javascripts/calendar.js
@@ -4,6 +4,7 @@
   if (!calendar_container) { return }

   calendar = new FullCalendar.Calendar(calendar_container, {
+    'initialDate': sessionStorage.getItem('lastDate') || null,
     'events': 'assignments.json',
     'startParam': 'start_date',
     'endParam': 'end_date',
@@ -15,6 +16,10 @@
         $('td[data-date=' + dateString + ']').removeClass('day-empty');
         date.setDate(date.getDate() + 1);
       }
+    },
+    'datesSet': function(info) {
+      var currentStart = info.view.currentStart.toISOString();
+      sessionStorage.setItem('lastDate', currentStart);
     }
   });
   calendar.render();
<|current_version|>
var calendar;
$(document).ready(function() {
  var calendar_container = document.getElementById('calendar');
  if (!calendar_container) { return }

  calendar = new FullCalendar.Calendar(calendar_container, {
    'initialDate': sessionStorage.getItem('lastDate') || null,
    'events': 'assignments.json',
    'startParam': 'start_date',
    'endParam': 'end_date',
    'dayCellClassNames': 'day-empty',
    'eventDidMount': function(info) {
      var date = info.event.start;
      while (date < info.event.end) {
        var dateString = date.toISOString().split('T')[0];
        $('td[data-date=' + dateString + ']').removeClass('day-empty');
        date.setDate(date.getDate() + 1);
      }
    },
    'datesSet': function(info) {
      var currentStart = info.view.currentStart.toISOString();
      sessionStorage.setItem('lastDate', currentStart);
    }
  });
  calendar.render();

  $('#calendar').on('click', 'td.day-empty', function() {
    window.location = 'assignments/new?date=' + $(this).data('date');
  });
});

<|next_version|>
var calendar;
$(document).ready(function() {
  var calendar_container = document.getElementById('calendar');
  if (!calendar_container) { return }

  calendar = new FullCalendar.Calendar(calendar_container, {
    'initialDate': sessionStorage.getItem('lastDate') || null,
    'events': 'assignments.json',
    'startParam': 'start_date',
    'endParam': 'end_date',
    'dayCellClassNames': 'day-empty',
    'eventDidMount': function(info) {
      var date = info.event.start;
      while (date < info.event.end) {
        var dateString = date.toISOString().split('T')[0];
        $('td[data-date=' + dateString + ']').removeClass('day-empty');
        date.setDate(date.getDate() + 1);
      }
    },
    'datesSet': function(info) {
      var currentStart = info.view.currentStart.toISOString();
      sessionStorage.setItem('lastDate', currentStart);
    }
  });

  calendar.render();

  $('#calendar').on('click', 'td.day-empty', function() {
    window.location = 'assignments/new?date=' + $(this).data('date');
  });
});


Add test for base64 encoded fonts

Add test for base64 encoded fonts

--- packages/fela-tools/src/__tests__/checkFontFormat-test.js
+++ packages/fela-tools/src/__tests__/checkFontFormat-test.js
@@ -4,9 +4,11 @@
   it('should return the correct format', () => {
     expect(checkFontFormat('foo.ttf')).toEqual('truetype')
     expect(checkFontFormat('foo.eot')).toEqual('embedded-opentype')
+    expect(checkFontFormat('data:application/x-font-ttf;base64,blahblahblahblahblahblah')).toEqual('truetype')
   })

   it('should return an empty string', () => {
     expect(checkFontFormat('foobar.png')).toEqual('')
+    expect(checkFontFormat('data:image/png;base64,blahblahblahblahblahblah')).toEqual('')
   })
 })

<|original_code|>
import checkFontFormat from '../checkFontFormat'

describe('Checking the font format', () => {
  it('should return the correct format', () => {
    expect(checkFontFormat('foo.ttf')).toEqual('truetype')
    expect(checkFontFormat('foo.eot')).toEqual('embedded-opentype')
  })

  it('should return an empty string', () => {
    expect(checkFontFormat('foobar.png')).toEqual('')
  })
})

<|edits_diff|>
--- packages/fela-tools/src/__tests__/checkFontFormat-test.js
+++ packages/fela-tools/src/__tests__/checkFontFormat-test.js
@@ -4,6 +4,7 @@
   it('should return the correct format', () => {
     expect(checkFontFormat('foo.ttf')).toEqual('truetype')
     expect(checkFontFormat('foo.eot')).toEqual('embedded-opentype')
+    expect(checkFontFormat('data:application/x-font-ttf;base64,blahblahblahblahblahblah')).toEqual('truetype')
   })

   it('should return an empty string', () => {
<|current_version|>
import checkFontFormat from '../checkFontFormat'

describe('Checking the font format', () => {
  it('should return the correct format', () => {
    expect(checkFontFormat('foo.ttf')).toEqual('truetype')
    expect(checkFontFormat('foo.eot')).toEqual('embedded-opentype')
    expect(checkFontFormat('data:application/x-font-ttf;base64,blahblahblahblahblahblah')).toEqual('truetype')
  })

  it('should return an empty string', () => {
    expect(checkFontFormat('foobar.png')).toEqual('')
  })
})

<|next_version|>
import checkFontFormat from '../checkFontFormat'

describe('Checking the font format', () => {
  it('should return the correct format', () => {
    expect(checkFontFormat('foo.ttf')).toEqual('truetype')
    expect(checkFontFormat('foo.eot')).toEqual('embedded-opentype')
    expect(checkFontFormat('data:application/x-font-ttf;base64,blahblahblahblahblahblah')).toEqual('truetype')
  })

  it('should return an empty string', () => {
    expect(checkFontFormat('foobar.png')).toEqual('')
    expect(checkFontFormat('data:image/png;base64,blahblahblahblahblahblah')).toEqual('')
  })
})


Add links to idvidual inventory scenes

Add links to idvidual inventory scenes

--- src/Scenes/InventoryScene.js
+++ src/Scenes/InventoryScene.js
@@ -34,12 +34,15 @@
     <InventoryDiv>
       <InventoryBox>
         <InventoryContainer name="malt" columns={maltColumns} displayLimit={5} />
+        View Full Table
       </InventoryBox>
       <InventoryBox>
         <InventoryContainer name="yeast" columns={yeastColumns} displayLimit={5} />
+        View Full Table
       </InventoryBox>
       <InventoryBox>
         <InventoryContainer name="hops" columns={hopColumns} displayLimit={5} />
+        View Full Table
       </InventoryBox>
     </InventoryDiv>
   )

<|original_code|>
import React from 'react'
import InventoryContainer from '../containers/InventoryContainer'
import ShadowBox from '../Components/ShadowBox'
import FlexDiv from '../Components/FlexDiv'

const InventoryBox = ShadowBox.extend`
  width:100%;
  margin:10px;
`

const InventoryDiv = FlexDiv.extend`
  width:50%;
  margin-top: 50px;
`

const InventoryScene = () => {

  const maltColumns = [
    {name: "Malt Name", type: "text"},
    {name: "Amount", type: "number"}
  ]

  const yeastColumns = [
    {name: "Yeast Name", type: "text"},
    {name: "Amount", type: "number"}
  ]

  const hopColumns = [
    {name: "Hop Name", type: "text"},
    {name: "Amount", type: "number"}
  ]

  return(
    <InventoryDiv>
      <InventoryBox>
        <InventoryContainer name="malt" columns={maltColumns} displayLimit={5} />
      </InventoryBox>
      <InventoryBox>
        <InventoryContainer name="yeast" columns={yeastColumns} displayLimit={5} />
      </InventoryBox>
      <InventoryBox>
        <InventoryContainer name="hops" columns={hopColumns} displayLimit={5} />
      </InventoryBox>
    </InventoryDiv>
  )
}

export default InventoryScene
<|edits_diff|>
--- src/Scenes/InventoryScene.js
+++ src/Scenes/InventoryScene.js
@@ -34,9 +34,11 @@
     <InventoryDiv>
       <InventoryBox>
         <InventoryContainer name="malt" columns={maltColumns} displayLimit={5} />
+        View Full Table
       </InventoryBox>
       <InventoryBox>
         <InventoryContainer name="yeast" columns={yeastColumns} displayLimit={5} />
+        View Full Table
       </InventoryBox>
       <InventoryBox>
         <InventoryContainer name="hops" columns={hopColumns} displayLimit={5} />
<|current_version|>
import React from 'react'
import InventoryContainer from '../containers/InventoryContainer'
import ShadowBox from '../Components/ShadowBox'
import FlexDiv from '../Components/FlexDiv'

const InventoryBox = ShadowBox.extend`
  width:100%;
  margin:10px;
`

const InventoryDiv = FlexDiv.extend`
  width:50%;
  margin-top: 50px;
`

const InventoryScene = () => {

  const maltColumns = [
    {name: "Malt Name", type: "text"},
    {name: "Amount", type: "number"}
  ]

  const yeastColumns = [
    {name: "Yeast Name", type: "text"},
    {name: "Amount", type: "number"}
  ]

  const hopColumns = [
    {name: "Hop Name", type: "text"},
    {name: "Amount", type: "number"}
  ]

  return(
    <InventoryDiv>
      <InventoryBox>
        <InventoryContainer name="malt" columns={maltColumns} displayLimit={5} />
        View Full Table
      </InventoryBox>
      <InventoryBox>
        <InventoryContainer name="yeast" columns={yeastColumns} displayLimit={5} />
        View Full Table
      </InventoryBox>
      <InventoryBox>
        <InventoryContainer name="hops" columns={hopColumns} displayLimit={5} />
      </InventoryBox>
    </InventoryDiv>
  )
}

export default InventoryScene
<|next_version|>
import React from 'react'
import InventoryContainer from '../containers/InventoryContainer'
import ShadowBox from '../Components/ShadowBox'
import FlexDiv from '../Components/FlexDiv'

const InventoryBox = ShadowBox.extend`
  width:100%;
  margin:10px;
`

const InventoryDiv = FlexDiv.extend`
  width:50%;
  margin-top: 50px;
`

const InventoryScene = () => {

  const maltColumns = [
    {name: "Malt Name", type: "text"},
    {name: "Amount", type: "number"}
  ]

  const yeastColumns = [
    {name: "Yeast Name", type: "text"},
    {name: "Amount", type: "number"}
  ]

  const hopColumns = [
    {name: "Hop Name", type: "text"},
    {name: "Amount", type: "number"}
  ]

  return(
    <InventoryDiv>
      <InventoryBox>
        <InventoryContainer name="malt" columns={maltColumns} displayLimit={5} />
        View Full Table
      </InventoryBox>
      <InventoryBox>
        <InventoryContainer name="yeast" columns={yeastColumns} displayLimit={5} />
        View Full Table
      </InventoryBox>
      <InventoryBox>
        <InventoryContainer name="hops" columns={hopColumns} displayLimit={5} />
        View Full Table
      </InventoryBox>
    </InventoryDiv>
  )
}

export default InventoryScene


Support voices and half operators as well

--- app/components/link-to-username/component.js
+++ app/components/link-to-username/component.js
@@ -10,6 +10,10 @@
   role: computed('username', function() {
     if (this.get('username').startsWith('@')) {
       return 'op';
+    } else if (this.get('username').startsWith('%')) {
+      return 'half-op';
+    } else if (this.get('username').startsWith('+')) {
+      return 'voice';
     } else {
       return 'normal';
     }
@@ -18,6 +22,10 @@
   usernameWithoutPrefix: computed('username', function() {
     if (this.get('username').startsWith('@')) {
       return this.get('username').replace('@', '');
+    } else if (this.get('username').startsWith('%')) {
+      return this.get('username').replace('%', '');
+    } else if (this.get('username').startsWith('+')) {
+      return this.get('username').replace('+', '');
     } else {
       return this.get('username');
     }

<|original_code|>
import Ember from 'ember';

const {
  computed
} = Ember;

export default Ember.Component.extend({
  username: null,

  role: computed('username', function() {
    if (this.get('username').startsWith('@')) {
      return 'op';
    } else {
      return 'normal';
    }
  }),

  usernameWithoutPrefix: computed('username', function() {
    if (this.get('username').startsWith('@')) {
      return this.get('username').replace('@', '');
    } else {
      return this.get('username');
    }
  }),
});

<|edits_diff|>
--- app/components/link-to-username/component.js
+++ app/components/link-to-username/component.js
@@ -10,6 +10,10 @@
   role: computed('username', function() {
     if (this.get('username').startsWith('@')) {
       return 'op';
+    } else if (this.get('username').startsWith('%')) {
+      return 'half-op';
+    } else if (this.get('username').startsWith('+')) {
+      return 'voice';
     } else {
       return 'normal';
     }
<|current_version|>
import Ember from 'ember';

const {
  computed
} = Ember;

export default Ember.Component.extend({
  username: null,

  role: computed('username', function() {
    if (this.get('username').startsWith('@')) {
      return 'op';
    } else if (this.get('username').startsWith('%')) {
      return 'half-op';
    } else if (this.get('username').startsWith('+')) {
      return 'voice';
    } else {
      return 'normal';
    }
  }),

  usernameWithoutPrefix: computed('username', function() {
    if (this.get('username').startsWith('@')) {
      return this.get('username').replace('@', '');
    } else {
      return this.get('username');
    }
  }),
});

<|next_version|>
import Ember from 'ember';

const {
  computed
} = Ember;

export default Ember.Component.extend({
  username: null,

  role: computed('username', function() {
    if (this.get('username').startsWith('@')) {
      return 'op';
    } else if (this.get('username').startsWith('%')) {
      return 'half-op';
    } else if (this.get('username').startsWith('+')) {
      return 'voice';
    } else {
      return 'normal';
    }
  }),

  usernameWithoutPrefix: computed('username', function() {
    if (this.get('username').startsWith('@')) {
      return this.get('username').replace('@', '');
    } else if (this.get('username').startsWith('%')) {
      return this.get('username').replace('%', '');
    } else if (this.get('username').startsWith('+')) {
      return this.get('username').replace('+', '');
    } else {
      return this.get('username');
    }
  }),
});


Add route for creating processes.

Add route for creating processes.

--- backend/server/resources/projects-routes.js
+++ backend/server/resources/projects-routes.js
@@ -6,8 +6,10 @@
     let router = require('koa-router')();
     let projects = require('./projects')(model.projects);
     let samples = require('./samples')(model.samples, schema);
+    let processes = require('./processes')(model.processes, schema);

     router.get('/projects', projects.all);
+    router.post('/projects/:project_id/processes', validateProjectAccess, processes.create);
     router.post('/projects/:project_id/samples', validateProjectAccess, samples.create);
     router.put('/projects/:project_id/samples/:sample_id', validateProjectAccess, samples.update);

<|original_code|>
module.exports = function(model) {
    'use strict';

    let validateProjectAccess = require('./project-access')(model.access);
    let schema = require('.././schema')(model);
    let router = require('koa-router')();
    let projects = require('./projects')(model.projects);
    let samples = require('./samples')(model.samples, schema);

    router.get('/projects', projects.all);
    router.post('/projects/:project_id/samples', validateProjectAccess, samples.create);
    router.put('/projects/:project_id/samples/:sample_id', validateProjectAccess, samples.update);

    return router;
};

<|edits_diff|>
--- backend/server/resources/projects-routes.js
+++ backend/server/resources/projects-routes.js
@@ -6,6 +6,7 @@
     let router = require('koa-router')();
     let projects = require('./projects')(model.projects);
     let samples = require('./samples')(model.samples, schema);
+    let processes = require('./processes')(model.processes, schema);

     router.get('/projects', projects.all);
     router.post('/projects/:project_id/samples', validateProjectAccess, samples.create);
<|current_version|>
module.exports = function(model) {
    'use strict';

    let validateProjectAccess = require('./project-access')(model.access);
    let schema = require('.././schema')(model);
    let router = require('koa-router')();
    let projects = require('./projects')(model.projects);
    let samples = require('./samples')(model.samples, schema);
    let processes = require('./processes')(model.processes, schema);

    router.get('/projects', projects.all);
    router.post('/projects/:project_id/samples', validateProjectAccess, samples.create);
    router.put('/projects/:project_id/samples/:sample_id', validateProjectAccess, samples.update);

    return router;
};

<|next_version|>
module.exports = function(model) {
    'use strict';

    let validateProjectAccess = require('./project-access')(model.access);
    let schema = require('.././schema')(model);
    let router = require('koa-router')();
    let projects = require('./projects')(model.projects);
    let samples = require('./samples')(model.samples, schema);
    let processes = require('./processes')(model.processes, schema);

    router.get('/projects', projects.all);
    router.post('/projects/:project_id/processes', validateProjectAccess, processes.create);
    router.post('/projects/:project_id/samples', validateProjectAccess, samples.create);
    router.put('/projects/:project_id/samples/:sample_id', validateProjectAccess, samples.update);

    return router;
};


Send ping/pong to keep the connection open.

--- app/assets/javascripts/threadable/realtime.js
+++ app/assets/javascripts/threadable/realtime.js
@@ -8,6 +8,10 @@
   if (Threadable.realtime.socketIo) {
     Threadable.realtime.socketIo.on('connect', function() {
       Threadable.realtime.socketIo.emit('realtime_user_id_connected', { userId: userId });
+
+      setInterval(function() {
+        Threadable.realtime.socketIo.emit('ping');
+      }, 10*1000);
     });

     Threadable.realtime.socketIo.on('application_update', function(message) {
@@ -21,5 +25,9 @@
       var update = Threadable.ApplicationUpdate.create(message);
       currentUser.get('applicationUpdates').pushObject(update);
     });
+
+    Threadable.realtime.socketIo.on('pong', function(message) {
+      // do nothing for now. but these do come back.
+    });
   }
 };

<|original_code|>
Threadable.realtime.connect = function(currentUser) {
  var userId = currentUser.get('userId');

  if (typeof io != 'undefined' && io !== null) {
    Threadable.realtime.socketIo = io.connect(Threadable.realtime.url + '/?userId=' + userId + '&token=' + Threadable.realtime.token);
  }

  if (Threadable.realtime.socketIo) {
    Threadable.realtime.socketIo.on('connect', function() {
      Threadable.realtime.socketIo.emit('realtime_user_id_connected', { userId: userId });
    });

    Threadable.realtime.socketIo.on('application_update', function(message) {
      // camelize the keys.
      Object.keys(message).map(function(key) {
        var value = message[key];
        delete message[key];
        message[Ember.String.camelize(key)] = value;
      }, this);

      var update = Threadable.ApplicationUpdate.create(message);
      currentUser.get('applicationUpdates').pushObject(update);
    });
  }
};

<|edits_diff|>
--- app/assets/javascripts/threadable/realtime.js
+++ app/assets/javascripts/threadable/realtime.js
@@ -8,6 +8,10 @@
   if (Threadable.realtime.socketIo) {
     Threadable.realtime.socketIo.on('connect', function() {
       Threadable.realtime.socketIo.emit('realtime_user_id_connected', { userId: userId });
+
+      setInterval(function() {
+        Threadable.realtime.socketIo.emit('ping');
+      }, 10*1000);
     });

     Threadable.realtime.socketIo.on('application_update', function(message) {
<|current_version|>
Threadable.realtime.connect = function(currentUser) {
  var userId = currentUser.get('userId');

  if (typeof io != 'undefined' && io !== null) {
    Threadable.realtime.socketIo = io.connect(Threadable.realtime.url + '/?userId=' + userId + '&token=' + Threadable.realtime.token);
  }

  if (Threadable.realtime.socketIo) {
    Threadable.realtime.socketIo.on('connect', function() {
      Threadable.realtime.socketIo.emit('realtime_user_id_connected', { userId: userId });

      setInterval(function() {
        Threadable.realtime.socketIo.emit('ping');
      }, 10*1000);
    });

    Threadable.realtime.socketIo.on('application_update', function(message) {
      // camelize the keys.
      Object.keys(message).map(function(key) {
        var value = message[key];
        delete message[key];
        message[Ember.String.camelize(key)] = value;
      }, this);

      var update = Threadable.ApplicationUpdate.create(message);
      currentUser.get('applicationUpdates').pushObject(update);
    });
  }
};

<|next_version|>
Threadable.realtime.connect = function(currentUser) {
  var userId = currentUser.get('userId');

  if (typeof io != 'undefined' && io !== null) {
    Threadable.realtime.socketIo = io.connect(Threadable.realtime.url + '/?userId=' + userId + '&token=' + Threadable.realtime.token);
  }

  if (Threadable.realtime.socketIo) {
    Threadable.realtime.socketIo.on('connect', function() {
      Threadable.realtime.socketIo.emit('realtime_user_id_connected', { userId: userId });

      setInterval(function() {
        Threadable.realtime.socketIo.emit('ping');
      }, 10*1000);
    });

    Threadable.realtime.socketIo.on('application_update', function(message) {
      // camelize the keys.
      Object.keys(message).map(function(key) {
        var value = message[key];
        delete message[key];
        message[Ember.String.camelize(key)] = value;
      }, this);

      var update = Threadable.ApplicationUpdate.create(message);
      currentUser.get('applicationUpdates').pushObject(update);
    });

    Threadable.realtime.socketIo.on('pong', function(message) {
      // do nothing for now. but these do come back.
    });
  }
};


Add the Salmon Run calendar to the hourly update script

--- src/splatoon/run.js
+++ src/splatoon/run.js
@@ -1,9 +1,11 @@
 const CronJob = require('cron').CronJob;
 const splatnet = require('./splatnet');
+const salmoncalendar = require('./salmoncalendar');

 console.info('Starting periodic tasks...');

 // Run every day at 0min 30sec after the hour
 new CronJob('30 0 * * * *', () => {
     splatnet.update();
+    salmoncalendar.update();
 }, null, true);

<|original_code|>
const CronJob = require('cron').CronJob;
const splatnet = require('./splatnet');

console.info('Starting periodic tasks...');

// Run every day at 0min 30sec after the hour
new CronJob('30 0 * * * *', () => {
    splatnet.update();
}, null, true);

<|edits_diff|>
--- src/splatoon/run.js
+++ src/splatoon/run.js
@@ -1,5 +1,6 @@
 const CronJob = require('cron').CronJob;
 const splatnet = require('./splatnet');
+const salmoncalendar = require('./salmoncalendar');

 console.info('Starting periodic tasks...');

<|current_version|>
const CronJob = require('cron').CronJob;
const splatnet = require('./splatnet');
const salmoncalendar = require('./salmoncalendar');

console.info('Starting periodic tasks...');

// Run every day at 0min 30sec after the hour
new CronJob('30 0 * * * *', () => {
    splatnet.update();
}, null, true);

<|next_version|>
const CronJob = require('cron').CronJob;
const splatnet = require('./splatnet');
const salmoncalendar = require('./salmoncalendar');

console.info('Starting periodic tasks...');

// Run every day at 0min 30sec after the hour
new CronJob('30 0 * * * *', () => {
    splatnet.update();
    salmoncalendar.update();
}, null, true);


Disable query form submissions when invalid

--- src/js/app/controllers/pagespeedQueryForm.controller.js
+++ src/js/app/controllers/pagespeedQueryForm.controller.js
@@ -9,9 +9,15 @@

 		vm.url = '';
 		vm.submit = submit;
+		vm.canNotSubmit = canNotSubmit;

 		function submit() {
 			pagespeedApi.run(vm.url);
 		}
+
+		function canNotSubmit() {
+			// On chrome since input[type=url] remains undefined unless value is actually a valid URL
+			return !vm.url; // FIXME also check if API key is set
+		}
 	}
 })();

<|original_code|>
(function() {
	'use strict';
	angular.module('pagespeedApp').controller('pagespeedQueryFormController', pagespeedQueryForm);

	pagespeedQueryForm.$inject = ['pagespeedApi'];

	function pagespeedQueryForm(pagespeedApi) {
		var vm = this;

		vm.url = '';
		vm.submit = submit;

		function submit() {
			pagespeedApi.run(vm.url);
		}
	}
})();

<|edits_diff|>
--- src/js/app/controllers/pagespeedQueryForm.controller.js
+++ src/js/app/controllers/pagespeedQueryForm.controller.js
@@ -9,6 +9,7 @@

 		vm.url = '';
 		vm.submit = submit;
+		vm.canNotSubmit = canNotSubmit;

 		function submit() {
 			pagespeedApi.run(vm.url);
<|current_version|>
(function() {
	'use strict';
	angular.module('pagespeedApp').controller('pagespeedQueryFormController', pagespeedQueryForm);

	pagespeedQueryForm.$inject = ['pagespeedApi'];

	function pagespeedQueryForm(pagespeedApi) {
		var vm = this;

		vm.url = '';
		vm.submit = submit;
		vm.canNotSubmit = canNotSubmit;

		function submit() {
			pagespeedApi.run(vm.url);
		}
	}
})();

<|next_version|>
(function() {
	'use strict';
	angular.module('pagespeedApp').controller('pagespeedQueryFormController', pagespeedQueryForm);

	pagespeedQueryForm.$inject = ['pagespeedApi'];

	function pagespeedQueryForm(pagespeedApi) {
		var vm = this;

		vm.url = '';
		vm.submit = submit;
		vm.canNotSubmit = canNotSubmit;

		function submit() {
			pagespeedApi.run(vm.url);
		}

		function canNotSubmit() {
			// On chrome since input[type=url] remains undefined unless value is actually a valid URL
			return !vm.url; // FIXME also check if API key is set
		}
	}
})();


Test preSetup and preTeardown callbacks

Test preSetup and preTeardown callbacks

--- tests/test-module-test.js
+++ tests/test-module-test.js
@@ -13,19 +13,35 @@
 };

 var a = 0;
+var b = 0;
+var preSetupOk = false;
+var preTeardownOk = false;

 moduleFor('component:x-foo', 'TestModule callbacks', {
   preSetup: function() {
     setResolverRegistry(registry);
+
+    preSetupOk = (a === 0);
+    b += 1;
   },

   setup: function() {
     a += 1;
   },

+  preTeardown: function() {
+    preTeardownOk = (a === 1);
+    b -= 1;
+  },
+
   teardown: function() {
     a -= 1;
   }
+});
+
+test("preSetup callback is called prior to any test setup", function() {
+  ok(preSetupOk);
+  equal(b, 1);
 });

 test("setup callback is called prior to test", function() {
@@ -35,3 +51,8 @@
 test("teardown callback is called after test", function() {
   equal(a, 1);
 });
+
+test("preTeardown callback is called prior to any test teardown", function() {
+  ok(preTeardownOk);
+  equal(b, 1);
+});

<|original_code|>
import { TestModule } from 'ember-test-helpers';
import test from 'tests/test-support/qunit-test';
import qunitModuleFor from 'tests/test-support/qunit-module-for';
import { setResolverRegistry } from 'tests/test-support/resolver';

function moduleFor(fullName, description, callbacks) {
  var module = new TestModule(fullName, description, callbacks);
  qunitModuleFor(module);
}

var registry = {
  'component:x-foo': Ember.Component.extend()
};

var a = 0;

moduleFor('component:x-foo', 'TestModule callbacks', {
  preSetup: function() {
    setResolverRegistry(registry);
  },

  setup: function() {
    a += 1;
  },

  teardown: function() {
    a -= 1;
  }
});

test("setup callback is called prior to test", function() {
  equal(a, 1);
});

test("teardown callback is called after test", function() {
  equal(a, 1);
});

<|edits_diff|>
--- tests/test-module-test.js
+++ tests/test-module-test.js
@@ -13,19 +13,35 @@
 };

 var a = 0;
+var b = 0;
+var preSetupOk = false;
+var preTeardownOk = false;

 moduleFor('component:x-foo', 'TestModule callbacks', {
   preSetup: function() {
     setResolverRegistry(registry);
+
+    preSetupOk = (a === 0);
+    b += 1;
   },

   setup: function() {
     a += 1;
   },

+  preTeardown: function() {
+    preTeardownOk = (a === 1);
+    b -= 1;
+  },
+
   teardown: function() {
     a -= 1;
   }
+});
+
+test("preSetup callback is called prior to any test setup", function() {
+  ok(preSetupOk);
+  equal(b, 1);
 });

 test("setup callback is called prior to test", function() {
<|current_version|>
import { TestModule } from 'ember-test-helpers';
import test from 'tests/test-support/qunit-test';
import qunitModuleFor from 'tests/test-support/qunit-module-for';
import { setResolverRegistry } from 'tests/test-support/resolver';

function moduleFor(fullName, description, callbacks) {
  var module = new TestModule(fullName, description, callbacks);
  qunitModuleFor(module);
}

var registry = {
  'component:x-foo': Ember.Component.extend()
};

var a = 0;
var b = 0;
var preSetupOk = false;
var preTeardownOk = false;

moduleFor('component:x-foo', 'TestModule callbacks', {
  preSetup: function() {
    setResolverRegistry(registry);

    preSetupOk = (a === 0);
    b += 1;
  },

  setup: function() {
    a += 1;
  },

  preTeardown: function() {
    preTeardownOk = (a === 1);
    b -= 1;
  },

  teardown: function() {
    a -= 1;
  }
});

test("preSetup callback is called prior to any test setup", function() {
  ok(preSetupOk);
  equal(b, 1);
});

test("setup callback is called prior to test", function() {
  equal(a, 1);
});

test("teardown callback is called after test", function() {
  equal(a, 1);
});

<|next_version|>
import { TestModule } from 'ember-test-helpers';
import test from 'tests/test-support/qunit-test';
import qunitModuleFor from 'tests/test-support/qunit-module-for';
import { setResolverRegistry } from 'tests/test-support/resolver';

function moduleFor(fullName, description, callbacks) {
  var module = new TestModule(fullName, description, callbacks);
  qunitModuleFor(module);
}

var registry = {
  'component:x-foo': Ember.Component.extend()
};

var a = 0;
var b = 0;
var preSetupOk = false;
var preTeardownOk = false;

moduleFor('component:x-foo', 'TestModule callbacks', {
  preSetup: function() {
    setResolverRegistry(registry);

    preSetupOk = (a === 0);
    b += 1;
  },

  setup: function() {
    a += 1;
  },

  preTeardown: function() {
    preTeardownOk = (a === 1);
    b -= 1;
  },

  teardown: function() {
    a -= 1;
  }
});

test("preSetup callback is called prior to any test setup", function() {
  ok(preSetupOk);
  equal(b, 1);
});

test("setup callback is called prior to test", function() {
  equal(a, 1);
});

test("teardown callback is called after test", function() {
  equal(a, 1);
});

test("preTeardown callback is called prior to any test teardown", function() {
  ok(preTeardownOk);
  equal(b, 1);
});


Index all scrolled models by perma id in entry state

--- entry_types/scrolled/package/src/entryState/watchCollections.js
+++ entry_types/scrolled/package/src/entryState/watchCollections.js
@@ -4,12 +4,14 @@
   watchCollection(chapters, {
     name: 'chapters',
     attributes: ['id', 'permaId'],
+    keyAttribute: 'permaId',
     includeConfiguration: true,
     dispatch
   });
   watchCollection(sections, {
     name: 'sections',
     attributes: ['id', 'permaId', 'chapterId'],
+    keyAttribute: 'permaId',
     includeConfiguration: true,
     dispatch
   });

<|original_code|>
import {watchCollection} from '../collections';

export function watchCollections({chapters, sections, contentElements, files}, {dispatch}) {
  watchCollection(chapters, {
    name: 'chapters',
    attributes: ['id', 'permaId'],
    includeConfiguration: true,
    dispatch
  });
  watchCollection(sections, {
    name: 'sections',
    attributes: ['id', 'permaId', 'chapterId'],
    includeConfiguration: true,
    dispatch
  });
  watchCollection(contentElements, {
    name: 'contentElements',
    attributes: ['id', 'permaId', 'typeName', 'sectionId'],
    keyAttribute: 'permaId',
    includeConfiguration: true,
    dispatch
  });

  Object.keys(files).forEach(collectionName => {
    watchCollection(files[collectionName], {
      name: camelize(collectionName),
      attributes: ['id', {permaId: 'perma_id'}, 'width', 'height', 'basename'],
      keyAttribute: 'permaId',
      includeConfiguration: true,
      dispatch
    });
  });
}

function camelize(snakeCase) {
  return snakeCase.replace(/_[a-z]/g, function(match) {
    return match[1].toUpperCase();
  });
}

<|edits_diff|>
--- entry_types/scrolled/package/src/entryState/watchCollections.js
+++ entry_types/scrolled/package/src/entryState/watchCollections.js
@@ -4,6 +4,7 @@
   watchCollection(chapters, {
     name: 'chapters',
     attributes: ['id', 'permaId'],
+    keyAttribute: 'permaId',
     includeConfiguration: true,
     dispatch
   });
<|current_version|>
import {watchCollection} from '../collections';

export function watchCollections({chapters, sections, contentElements, files}, {dispatch}) {
  watchCollection(chapters, {
    name: 'chapters',
    attributes: ['id', 'permaId'],
    keyAttribute: 'permaId',
    includeConfiguration: true,
    dispatch
  });
  watchCollection(sections, {
    name: 'sections',
    attributes: ['id', 'permaId', 'chapterId'],
    includeConfiguration: true,
    dispatch
  });
  watchCollection(contentElements, {
    name: 'contentElements',
    attributes: ['id', 'permaId', 'typeName', 'sectionId'],
    keyAttribute: 'permaId',
    includeConfiguration: true,
    dispatch
  });

  Object.keys(files).forEach(collectionName => {
    watchCollection(files[collectionName], {
      name: camelize(collectionName),
      attributes: ['id', {permaId: 'perma_id'}, 'width', 'height', 'basename'],
      keyAttribute: 'permaId',
      includeConfiguration: true,
      dispatch
    });
  });
}

function camelize(snakeCase) {
  return snakeCase.replace(/_[a-z]/g, function(match) {
    return match[1].toUpperCase();
  });
}

<|next_version|>
import {watchCollection} from '../collections';

export function watchCollections({chapters, sections, contentElements, files}, {dispatch}) {
  watchCollection(chapters, {
    name: 'chapters',
    attributes: ['id', 'permaId'],
    keyAttribute: 'permaId',
    includeConfiguration: true,
    dispatch
  });
  watchCollection(sections, {
    name: 'sections',
    attributes: ['id', 'permaId', 'chapterId'],
    keyAttribute: 'permaId',
    includeConfiguration: true,
    dispatch
  });
  watchCollection(contentElements, {
    name: 'contentElements',
    attributes: ['id', 'permaId', 'typeName', 'sectionId'],
    keyAttribute: 'permaId',
    includeConfiguration: true,
    dispatch
  });

  Object.keys(files).forEach(collectionName => {
    watchCollection(files[collectionName], {
      name: camelize(collectionName),
      attributes: ['id', {permaId: 'perma_id'}, 'width', 'height', 'basename'],
      keyAttribute: 'permaId',
      includeConfiguration: true,
      dispatch
    });
  });
}

function camelize(snakeCase) {
  return snakeCase.replace(/_[a-z]/g, function(match) {
    return match[1].toUpperCase();
  });
}


Clarify what the happens in argument analysis

--- ast/call.js
+++ ast/call.js
@@ -16,9 +16,11 @@
     const numRequiredParams = callee.requiredParameterNames.size;
     const numParams = callee.allParameterNames.size;
     if (numArgs < numRequiredParams) {
+      // We have to at least cover all the required parameters
       throw new Error(`Expected at least ${numRequiredParams} arguments but called with ${numArgs}`);
     }
     if (numArgs > numParams) {
+      // We can't pass more arguments than the total number of parameters
       throw new Error(`Expected at most ${numParams} arguments but called with ${numArgs}`);
     }
   }

<|original_code|>
module.exports = class Call {
  constructor(callee, args) {
    this.callee = callee;
    this.args = args;
  }

  analyze(context) {
    this.callee.analyze(context);
    context.assertIsFunction(this.callee.referent);
    this.checkNumberOfArguments(this.callee.referent);
    this.checkArgumentNamesAndPositionalRules(this.callee.referent);
  }

  checkNumberOfArguments(callee) {
    const numArgs = this.args.length;
    const numRequiredParams = callee.requiredParameterNames.size;
    const numParams = callee.allParameterNames.size;
    if (numArgs < numRequiredParams) {
      throw new Error(`Expected at least ${numRequiredParams} arguments but called with ${numArgs}`);
    }
    if (numArgs > numParams) {
      throw new Error(`Expected at most ${numParams} arguments but called with ${numArgs}`);
    }
  }

  checkArgumentNamesAndPositionalRules(callee) {
    let keywordArgumentSeen = false;
    this.args.forEach((arg) => {
      if (arg.id) {
        // This is a keyword argument, record that fact and check that it's okay
        keywordArgumentSeen = true;
        if (!callee.allParameterNames.has(arg.id)) {
          throw new Error(`Function does not have a parameter called ${arg.id}`);
        }
      } else if (keywordArgumentSeen) {
        // This is a positional argument, but a prior one was a keyword one
        throw new Error('Positional argument in call after keyword argument');
      }
    });
  }
};

<|edits_diff|>
--- ast/call.js
+++ ast/call.js
@@ -16,6 +16,7 @@
     const numRequiredParams = callee.requiredParameterNames.size;
     const numParams = callee.allParameterNames.size;
     if (numArgs < numRequiredParams) {
+      // We have to at least cover all the required parameters
       throw new Error(`Expected at least ${numRequiredParams} arguments but called with ${numArgs}`);
     }
     if (numArgs > numParams) {
<|current_version|>
module.exports = class Call {
  constructor(callee, args) {
    this.callee = callee;
    this.args = args;
  }

  analyze(context) {
    this.callee.analyze(context);
    context.assertIsFunction(this.callee.referent);
    this.checkNumberOfArguments(this.callee.referent);
    this.checkArgumentNamesAndPositionalRules(this.callee.referent);
  }

  checkNumberOfArguments(callee) {
    const numArgs = this.args.length;
    const numRequiredParams = callee.requiredParameterNames.size;
    const numParams = callee.allParameterNames.size;
    if (numArgs < numRequiredParams) {
      // We have to at least cover all the required parameters
      throw new Error(`Expected at least ${numRequiredParams} arguments but called with ${numArgs}`);
    }
    if (numArgs > numParams) {
      throw new Error(`Expected at most ${numParams} arguments but called with ${numArgs}`);
    }
  }

  checkArgumentNamesAndPositionalRules(callee) {
    let keywordArgumentSeen = false;
    this.args.forEach((arg) => {
      if (arg.id) {
        // This is a keyword argument, record that fact and check that it's okay
        keywordArgumentSeen = true;
        if (!callee.allParameterNames.has(arg.id)) {
          throw new Error(`Function does not have a parameter called ${arg.id}`);
        }
      } else if (keywordArgumentSeen) {
        // This is a positional argument, but a prior one was a keyword one
        throw new Error('Positional argument in call after keyword argument');
      }
    });
  }
};

<|next_version|>
module.exports = class Call {
  constructor(callee, args) {
    this.callee = callee;
    this.args = args;
  }

  analyze(context) {
    this.callee.analyze(context);
    context.assertIsFunction(this.callee.referent);
    this.checkNumberOfArguments(this.callee.referent);
    this.checkArgumentNamesAndPositionalRules(this.callee.referent);
  }

  checkNumberOfArguments(callee) {
    const numArgs = this.args.length;
    const numRequiredParams = callee.requiredParameterNames.size;
    const numParams = callee.allParameterNames.size;
    if (numArgs < numRequiredParams) {
      // We have to at least cover all the required parameters
      throw new Error(`Expected at least ${numRequiredParams} arguments but called with ${numArgs}`);
    }
    if (numArgs > numParams) {
      // We can't pass more arguments than the total number of parameters
      throw new Error(`Expected at most ${numParams} arguments but called with ${numArgs}`);
    }
  }

  checkArgumentNamesAndPositionalRules(callee) {
    let keywordArgumentSeen = false;
    this.args.forEach((arg) => {
      if (arg.id) {
        // This is a keyword argument, record that fact and check that it's okay
        keywordArgumentSeen = true;
        if (!callee.allParameterNames.has(arg.id)) {
          throw new Error(`Function does not have a parameter called ${arg.id}`);
        }
      } else if (keywordArgumentSeen) {
        // This is a positional argument, but a prior one was a keyword one
        throw new Error('Positional argument in call after keyword argument');
      }
    });
  }
};


Make sure that the select queries return records otherwise, bail

--- migrations/6_update_dates_existing_published_projects.js
+++ migrations/6_update_dates_existing_published_projects.js
@@ -6,6 +6,10 @@
   .orWhereNull('date_updated')
   .select('id', 'date_created', 'date_updated')
   .then(function(publishedProjects) {
+    if (!publishedProjects) {
+      return;
+    }
+
     return Promise.map(publishedProjects, function(publishedProject) {
       var publishedProjectId = publishedProject.id;
       var dateUpdated = publishedProject.date_updated;
@@ -24,6 +28,10 @@
       return knex('projects')
       .where('published_id', publishedProjectId).select('date_updated')
       .then(function(projects) {
+        if (!projects || !projects[0]) {
+          return;
+        }
+
         var date = projects[0].date_updated;

         // Update the date_created and date_updated fields in the

<|original_code|>
'use strict';

exports.up = function (knex, Promise) {
  return knex('publishedProjects')
  .whereNull('date_created')
  .orWhereNull('date_updated')
  .select('id', 'date_created', 'date_updated')
  .then(function(publishedProjects) {
    return Promise.map(publishedProjects, function(publishedProject) {
      var publishedProjectId = publishedProject.id;
      var dateUpdated = publishedProject.date_updated;

      // For existing published projects that have been updated since
      // date tracking has been added, they will have a date_updated
      // but not a date_created. Set the date_created to the date_updated
      if (dateUpdated) {
        return knex('publishedProjects').update('date_created', dateUpdated)
        .where('id', publishedProjectId);
      }

      // For existing published projects that have not been updated
      // since date tracking has been added - set both the dates
      // to the date_updated of the corresponding project
      return knex('projects')
      .where('published_id', publishedProjectId).select('date_updated')
      .then(function(projects) {
        var date = projects[0].date_updated;

        // Update the date_created and date_updated fields in the
        // publishedProjects table
        return knex('publishedProjects')
        .where('id', publishedProjectId)
        .update({
          date_created: date,
          date_updated: date
        });
      });
    });
  });
};

exports.down = function (knex, Promise) {
  // This is an "update once" migration so that we do not lose data
  return Promise.resolve();
};

<|edits_diff|>
--- migrations/6_update_dates_existing_published_projects.js
+++ migrations/6_update_dates_existing_published_projects.js
@@ -6,6 +6,10 @@
   .orWhereNull('date_updated')
   .select('id', 'date_created', 'date_updated')
   .then(function(publishedProjects) {
+    if (!publishedProjects) {
+      return;
+    }
+
     return Promise.map(publishedProjects, function(publishedProject) {
       var publishedProjectId = publishedProject.id;
       var dateUpdated = publishedProject.date_updated;
<|current_version|>
'use strict';

exports.up = function (knex, Promise) {
  return knex('publishedProjects')
  .whereNull('date_created')
  .orWhereNull('date_updated')
  .select('id', 'date_created', 'date_updated')
  .then(function(publishedProjects) {
    if (!publishedProjects) {
      return;
    }

    return Promise.map(publishedProjects, function(publishedProject) {
      var publishedProjectId = publishedProject.id;
      var dateUpdated = publishedProject.date_updated;

      // For existing published projects that have been updated since
      // date tracking has been added, they will have a date_updated
      // but not a date_created. Set the date_created to the date_updated
      if (dateUpdated) {
        return knex('publishedProjects').update('date_created', dateUpdated)
        .where('id', publishedProjectId);
      }

      // For existing published projects that have not been updated
      // since date tracking has been added - set both the dates
      // to the date_updated of the corresponding project
      return knex('projects')
      .where('published_id', publishedProjectId).select('date_updated')
      .then(function(projects) {
        var date = projects[0].date_updated;

        // Update the date_created and date_updated fields in the
        // publishedProjects table
        return knex('publishedProjects')
        .where('id', publishedProjectId)
        .update({
          date_created: date,
          date_updated: date
        });
      });
    });
  });
};

exports.down = function (knex, Promise) {
  // This is an "update once" migration so that we do not lose data
  return Promise.resolve();
};

<|next_version|>
'use strict';

exports.up = function (knex, Promise) {
  return knex('publishedProjects')
  .whereNull('date_created')
  .orWhereNull('date_updated')
  .select('id', 'date_created', 'date_updated')
  .then(function(publishedProjects) {
    if (!publishedProjects) {
      return;
    }

    return Promise.map(publishedProjects, function(publishedProject) {
      var publishedProjectId = publishedProject.id;
      var dateUpdated = publishedProject.date_updated;

      // For existing published projects that have been updated since
      // date tracking has been added, they will have a date_updated
      // but not a date_created. Set the date_created to the date_updated
      if (dateUpdated) {
        return knex('publishedProjects').update('date_created', dateUpdated)
        .where('id', publishedProjectId);
      }

      // For existing published projects that have not been updated
      // since date tracking has been added - set both the dates
      // to the date_updated of the corresponding project
      return knex('projects')
      .where('published_id', publishedProjectId).select('date_updated')
      .then(function(projects) {
        if (!projects || !projects[0]) {
          return;
        }

        var date = projects[0].date_updated;

        // Update the date_created and date_updated fields in the
        // publishedProjects table
        return knex('publishedProjects')
        .where('id', publishedProjectId)
        .update({
          date_created: date,
          date_updated: date
        });
      });
    });
  });
};

exports.down = function (knex, Promise) {
  // This is an "update once" migration so that we do not lose data
  return Promise.resolve();
};


Add a guard clause to ResponseNewWindow

Add a guard clause to ResponseNewWindow

--- Resources/ui/handheld/android/ResponsesNewWindow.js
+++ Resources/ui/handheld/android/ResponsesNewWindow.js
@@ -19,7 +19,9 @@
   });

   var confirmDialog = new ConfirmDialog(L("confirm"), L("confirm_clear_answers"), onConfirm = function(e) {
+    if(view) {
     view.cleanup();
+  }
     view = null;
     self.close();
   });

<|original_code|>
function ResponsesNewWindow(surveyID) {
  try {
  var ResponsesIndexView = require('ui/common/responses/ResponsesIndexView');
  var ResponsesNewView = require('ui/common/responses/ResponsesNewView');
  var ConfirmDialog = require('ui/common/components/ConfirmDialog');

  var self = Ti.UI.createWindow({
    navBarHidden : true,
    backgroundColor : "#fff"
  });
  var view = new ResponsesNewView(surveyID);
  self.add(view);

  view.addEventListener('ResponsesNewView:savedResponse', function() {
    Ti.App.fireEvent('ResponseNewWindow:closed');
    view.cleanup();
    view = null;
    self.close();
  });

  var confirmDialog = new ConfirmDialog(L("confirm"), L("confirm_clear_answers"), onConfirm = function(e) {
    view.cleanup();
    view = null;
    self.close();
  });

  self.addEventListener('android:back', function() {
    confirmDialog.show();
  });

  return self;
  }
  catch(e) {
    var auditor = require('helpers/Auditor');
    auditor.writeIntoAuditFile(arguments.callee.name + " - " + e.toString());
  }
}

module.exports = ResponsesNewWindow;

<|edits_diff|>
--- Resources/ui/handheld/android/ResponsesNewWindow.js
+++ Resources/ui/handheld/android/ResponsesNewWindow.js
@@ -19,6 +19,7 @@
   });

   var confirmDialog = new ConfirmDialog(L("confirm"), L("confirm_clear_answers"), onConfirm = function(e) {
+    if(view) {
     view.cleanup();
     view = null;
     self.close();
<|current_version|>
function ResponsesNewWindow(surveyID) {
  try {
  var ResponsesIndexView = require('ui/common/responses/ResponsesIndexView');
  var ResponsesNewView = require('ui/common/responses/ResponsesNewView');
  var ConfirmDialog = require('ui/common/components/ConfirmDialog');

  var self = Ti.UI.createWindow({
    navBarHidden : true,
    backgroundColor : "#fff"
  });
  var view = new ResponsesNewView(surveyID);
  self.add(view);

  view.addEventListener('ResponsesNewView:savedResponse', function() {
    Ti.App.fireEvent('ResponseNewWindow:closed');
    view.cleanup();
    view = null;
    self.close();
  });

  var confirmDialog = new ConfirmDialog(L("confirm"), L("confirm_clear_answers"), onConfirm = function(e) {
    if(view) {
    view.cleanup();
    view = null;
    self.close();
  });

  self.addEventListener('android:back', function() {
    confirmDialog.show();
  });

  return self;
  }
  catch(e) {
    var auditor = require('helpers/Auditor');
    auditor.writeIntoAuditFile(arguments.callee.name + " - " + e.toString());
  }
}

module.exports = ResponsesNewWindow;

<|next_version|>
function ResponsesNewWindow(surveyID) {
  try {
  var ResponsesIndexView = require('ui/common/responses/ResponsesIndexView');
  var ResponsesNewView = require('ui/common/responses/ResponsesNewView');
  var ConfirmDialog = require('ui/common/components/ConfirmDialog');

  var self = Ti.UI.createWindow({
    navBarHidden : true,
    backgroundColor : "#fff"
  });
  var view = new ResponsesNewView(surveyID);
  self.add(view);

  view.addEventListener('ResponsesNewView:savedResponse', function() {
    Ti.App.fireEvent('ResponseNewWindow:closed');
    view.cleanup();
    view = null;
    self.close();
  });

  var confirmDialog = new ConfirmDialog(L("confirm"), L("confirm_clear_answers"), onConfirm = function(e) {
    if(view) {
    view.cleanup();
  }
    view = null;
    self.close();
  });

  self.addEventListener('android:back', function() {
    confirmDialog.show();
  });

  return self;
  }
  catch(e) {
    var auditor = require('helpers/Auditor');
    auditor.writeIntoAuditFile(arguments.callee.name + " - " + e.toString());
  }
}

module.exports = ResponsesNewWindow;


Add more asserts to tests for error checking

--- test/controller-integration-spec.js
+++ test/controller-integration-spec.js
@@ -12,6 +12,7 @@
     it('it should give an expected output on input', function(done) {
         this.controllerInterface.runController([-0.4, 3.1],
             function(err, result) {
+                assert.ifError(err);
                 assertFloatEqual(result, 0.5);
                 done();
         });
@@ -20,6 +21,7 @@
     it('it should give an expected output on input', function(done) {
         this.controllerInterface.runController([0.2, 1.1],
             function(err, result) {
+                assert.ifError(err);
                 assertFloatEqual(result, 1.5);
                 done();
         });

<|original_code|>
'use strict';

var assert = require('assert');
var ControllerInterface = require('../lib/controller-interface');

describe('controller-interface', function() {
    beforeEach(function() {
        this.controllerInterface = new ControllerInterface(
            './test-controller.fcl');
    });

    it('it should give an expected output on input', function(done) {
        this.controllerInterface.runController([-0.4, 3.1],
            function(err, result) {
                assertFloatEqual(result, 0.5);
                done();
        });
    });

    it('it should give an expected output on input', function(done) {
        this.controllerInterface.runController([0.2, 1.1],
            function(err, result) {
                assertFloatEqual(result, 1.5);
                done();
        });
    });
});

function assertFloatEqual(actual, expected) {
    assert(Math.abs(actual - expected) < 1e-5, 'expected ' + expected + ', got ' + actual);
}

<|edits_diff|>
--- test/controller-integration-spec.js
+++ test/controller-integration-spec.js
@@ -12,6 +12,7 @@
     it('it should give an expected output on input', function(done) {
         this.controllerInterface.runController([-0.4, 3.1],
             function(err, result) {
+                assert.ifError(err);
                 assertFloatEqual(result, 0.5);
                 done();
         });
<|current_version|>
'use strict';

var assert = require('assert');
var ControllerInterface = require('../lib/controller-interface');

describe('controller-interface', function() {
    beforeEach(function() {
        this.controllerInterface = new ControllerInterface(
            './test-controller.fcl');
    });

    it('it should give an expected output on input', function(done) {
        this.controllerInterface.runController([-0.4, 3.1],
            function(err, result) {
                assert.ifError(err);
                assertFloatEqual(result, 0.5);
                done();
        });
    });

    it('it should give an expected output on input', function(done) {
        this.controllerInterface.runController([0.2, 1.1],
            function(err, result) {
                assertFloatEqual(result, 1.5);
                done();
        });
    });
});

function assertFloatEqual(actual, expected) {
    assert(Math.abs(actual - expected) < 1e-5, 'expected ' + expected + ', got ' + actual);
}

<|next_version|>
'use strict';

var assert = require('assert');
var ControllerInterface = require('../lib/controller-interface');

describe('controller-interface', function() {
    beforeEach(function() {
        this.controllerInterface = new ControllerInterface(
            './test-controller.fcl');
    });

    it('it should give an expected output on input', function(done) {
        this.controllerInterface.runController([-0.4, 3.1],
            function(err, result) {
                assert.ifError(err);
                assertFloatEqual(result, 0.5);
                done();
        });
    });

    it('it should give an expected output on input', function(done) {
        this.controllerInterface.runController([0.2, 1.1],
            function(err, result) {
                assert.ifError(err);
                assertFloatEqual(result, 1.5);
                done();
        });
    });
});

function assertFloatEqual(actual, expected) {
    assert(Math.abs(actual - expected) < 1e-5, 'expected ' + expected + ', got ' + actual);
}


Update the list of void elements

Update the list of void elements

Closes #1262

--- lib/self-closing.js
+++ lib/self-closing.js
@@ -10,10 +10,16 @@
   , 'base'
   , 'br'
   , 'col'
+  , 'embed'
   , 'hr'
   , 'img'
   , 'input'
+  , 'keygen'
   , 'link'
+  , 'menuitem'
   , 'meta'
+  , 'param'
   , 'source'
+  , 'track'
+  , 'wbr'
 ];

<|original_code|>

/*!
 * Jade - self closing tags
 * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
 * MIT Licensed
 */

module.exports = [
    'area'
  , 'base'
  , 'br'
  , 'col'
  , 'hr'
  , 'img'
  , 'input'
  , 'link'
  , 'meta'
  , 'source'
];

<|edits_diff|>
--- lib/self-closing.js
+++ lib/self-closing.js
@@ -10,10 +10,14 @@
   , 'base'
   , 'br'
   , 'col'
+  , 'embed'
   , 'hr'
   , 'img'
   , 'input'
+  , 'keygen'
   , 'link'
+  , 'menuitem'
   , 'meta'
+  , 'param'
   , 'source'
 ];
<|current_version|>

/*!
 * Jade - self closing tags
 * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
 * MIT Licensed
 */

module.exports = [
    'area'
  , 'base'
  , 'br'
  , 'col'
  , 'embed'
  , 'hr'
  , 'img'
  , 'input'
  , 'keygen'
  , 'link'
  , 'menuitem'
  , 'meta'
  , 'param'
  , 'source'
];

<|next_version|>

/*!
 * Jade - self closing tags
 * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
 * MIT Licensed
 */

module.exports = [
    'area'
  , 'base'
  , 'br'
  , 'col'
  , 'embed'
  , 'hr'
  , 'img'
  , 'input'
  , 'keygen'
  , 'link'
  , 'menuitem'
  , 'meta'
  , 'param'
  , 'source'
  , 'track'
  , 'wbr'
];


Add most desired maintenance intervals: 14 days and 30 days

--- src/Domain/Maintenance.js
+++ src/Domain/Maintenance.js
@@ -7,6 +7,8 @@
     sixHours: "sixHours",
     oneDay: "oneDay",
     oneWeek: "oneWeek",
+    twoWeeks: "twoWeeks",
+    oneMonth: "oneMonth",
 };

 export const MaintenanceTimes = {
@@ -17,6 +19,8 @@
     sixHours: 360,
     oneDay: 1440,
     oneWeek: 10080,
+    twoWeeks: 20160,
+    oneMonth: 43200,
 };

 export const MaintenanceCaptions = {
@@ -27,6 +31,8 @@
     sixHours: "6 hours",
     oneDay: "1 day",
     oneWeek: "1 week",
+    twoWeeks: "2 weeks",
+    oneMonth: "1 month",
 };

 export type Maintenance = $Keys<typeof Maintenances>;

<|original_code|>
// @flow
export const Maintenances = {
    off: "off",
    quarterHour: "quarterHour",
    oneHour: "oneHour",
    threeHours: "threeHours",
    sixHours: "sixHours",
    oneDay: "oneDay",
    oneWeek: "oneWeek",
};

export const MaintenanceTimes = {
    off: 0,
    quarterHour: 15,
    oneHour: 60,
    threeHours: 180,
    sixHours: 360,
    oneDay: 1440,
    oneWeek: 10080,
};

export const MaintenanceCaptions = {
    off: "Off",
    quarterHour: "15 min",
    oneHour: "1 hour",
    threeHours: "3 hours",
    sixHours: "6 hours",
    oneDay: "1 day",
    oneWeek: "1 week",
};

export type Maintenance = $Keys<typeof Maintenances>;

export function getMaintenanceCaption(maintenance: Maintenance): string {
    return MaintenanceCaptions[maintenance];
}

export function getMaintenanceTime(maintenance: Maintenance): number {
    return MaintenanceTimes[maintenance];
}

<|edits_diff|>
--- src/Domain/Maintenance.js
+++ src/Domain/Maintenance.js
@@ -7,6 +7,8 @@
     sixHours: "sixHours",
     oneDay: "oneDay",
     oneWeek: "oneWeek",
+    twoWeeks: "twoWeeks",
+    oneMonth: "oneMonth",
 };

 export const MaintenanceTimes = {
@@ -17,6 +19,8 @@
     sixHours: 360,
     oneDay: 1440,
     oneWeek: 10080,
+    twoWeeks: 20160,
+    oneMonth: 43200,
 };

 export const MaintenanceCaptions = {
<|current_version|>
// @flow
export const Maintenances = {
    off: "off",
    quarterHour: "quarterHour",
    oneHour: "oneHour",
    threeHours: "threeHours",
    sixHours: "sixHours",
    oneDay: "oneDay",
    oneWeek: "oneWeek",
    twoWeeks: "twoWeeks",
    oneMonth: "oneMonth",
};

export const MaintenanceTimes = {
    off: 0,
    quarterHour: 15,
    oneHour: 60,
    threeHours: 180,
    sixHours: 360,
    oneDay: 1440,
    oneWeek: 10080,
    twoWeeks: 20160,
    oneMonth: 43200,
};

export const MaintenanceCaptions = {
    off: "Off",
    quarterHour: "15 min",
    oneHour: "1 hour",
    threeHours: "3 hours",
    sixHours: "6 hours",
    oneDay: "1 day",
    oneWeek: "1 week",
};

export type Maintenance = $Keys<typeof Maintenances>;

export function getMaintenanceCaption(maintenance: Maintenance): string {
    return MaintenanceCaptions[maintenance];
}

export function getMaintenanceTime(maintenance: Maintenance): number {
    return MaintenanceTimes[maintenance];
}

<|next_version|>
// @flow
export const Maintenances = {
    off: "off",
    quarterHour: "quarterHour",
    oneHour: "oneHour",
    threeHours: "threeHours",
    sixHours: "sixHours",
    oneDay: "oneDay",
    oneWeek: "oneWeek",
    twoWeeks: "twoWeeks",
    oneMonth: "oneMonth",
};

export const MaintenanceTimes = {
    off: 0,
    quarterHour: 15,
    oneHour: 60,
    threeHours: 180,
    sixHours: 360,
    oneDay: 1440,
    oneWeek: 10080,
    twoWeeks: 20160,
    oneMonth: 43200,
};

export const MaintenanceCaptions = {
    off: "Off",
    quarterHour: "15 min",
    oneHour: "1 hour",
    threeHours: "3 hours",
    sixHours: "6 hours",
    oneDay: "1 day",
    oneWeek: "1 week",
    twoWeeks: "2 weeks",
    oneMonth: "1 month",
};

export type Maintenance = $Keys<typeof Maintenances>;

export function getMaintenanceCaption(maintenance: Maintenance): string {
    return MaintenanceCaptions[maintenance];
}

export function getMaintenanceTime(maintenance: Maintenance): number {
    return MaintenanceTimes[maintenance];
}


Add more testing to ensure function is equivalent to prior code

--- src/candela/util/test/util.js
+++ src/candela/util/test/util.js
@@ -1,9 +1,14 @@
 import test from 'tape-catch';
 import { inferAll } from '..';
+import { read } from 'datalib';

 test('inferAll()', t => {
   t.deepEqual(inferAll([{'a.b': 1}]), {'a.b': 'integer'}, 'should work on fields with nested dots');
   t.deepEqual(inferAll([{'a': {'b': 1}}]), {'a': 'string'}, 'should only accept top-level fields');

+  let testData = [{a: 1, b: 'foo', c: 1.5}];
+  read(testData, {parse: 'auto'});
+  t.deepEqual(testData.__types__, inferAll(testData), 'should work identically to datalib read() when no nested dots');
+
   t.end();
 });

<|original_code|>
import test from 'tape-catch';
import { inferAll } from '..';

test('inferAll()', t => {
  t.deepEqual(inferAll([{'a.b': 1}]), {'a.b': 'integer'}, 'should work on fields with nested dots');
  t.deepEqual(inferAll([{'a': {'b': 1}}]), {'a': 'string'}, 'should only accept top-level fields');

  t.end();
});

<|edits_diff|>
--- src/candela/util/test/util.js
+++ src/candela/util/test/util.js
@@ -1,5 +1,6 @@
 import test from 'tape-catch';
 import { inferAll } from '..';
+import { read } from 'datalib';

 test('inferAll()', t => {
   t.deepEqual(inferAll([{'a.b': 1}]), {'a.b': 'integer'}, 'should work on fields with nested dots');
<|current_version|>
import test from 'tape-catch';
import { inferAll } from '..';
import { read } from 'datalib';

test('inferAll()', t => {
  t.deepEqual(inferAll([{'a.b': 1}]), {'a.b': 'integer'}, 'should work on fields with nested dots');
  t.deepEqual(inferAll([{'a': {'b': 1}}]), {'a': 'string'}, 'should only accept top-level fields');

  t.end();
});

<|next_version|>
import test from 'tape-catch';
import { inferAll } from '..';
import { read } from 'datalib';

test('inferAll()', t => {
  t.deepEqual(inferAll([{'a.b': 1}]), {'a.b': 'integer'}, 'should work on fields with nested dots');
  t.deepEqual(inferAll([{'a': {'b': 1}}]), {'a': 'string'}, 'should only accept top-level fields');

  let testData = [{a: 1, b: 'foo', c: 1.5}];
  read(testData, {parse: 'auto'});
  t.deepEqual(testData.__types__, inferAll(testData), 'should work identically to datalib read() when no nested dots');

  t.end();
});


Add runCmd method to execute CMD handlers

--- test/lib/TempDir.js
+++ test/lib/TempDir.js
@@ -2,6 +2,8 @@
 //

 'use strict';
+
+var cmds = require('../../lib/commands');

 var fs = require('fs');
 var path = require('path');
@@ -37,6 +39,11 @@
     return fs.accessSync(path.join(this.tmpLocation, name), fs.F_OK);
   },

+  runCmd: function (cmd, argv) {
+    argv = [cmd].concat(argv);
+    return cmds[cmd](argv, this.tmpLocation);
+  },
+
   collider: function (args) {
     args = args || [];
     return spawnSync('collider', args, { cwd: this.tmpLocation });

<|original_code|>
// test/lib/TempDir.js
//

'use strict';

var fs = require('fs');
var path = require('path');
var spawnSync = require('child_process').spawnSync;

var mkdirp = require('mkdirp');
var rimraf = require('rimraf');

var TempDir = {
  tmpLocation: null,

  prepare: function () {
    mkdirp.sync(this.tmpLocation);
  },

  clean: function () {
    rimraf.sync(this.tmpLocation);
  },

  getPath: function (name) {
    return path.join(this.tmpLocation, name);
  },

  read: function (name) {
    return fs.readFileSync(this.getPath(name), 'utf8');
  },

  readJson: function (name) {
    return JSON.parse(this.read(name));
  },

  exists: function (name) {
    return fs.accessSync(path.join(this.tmpLocation, name), fs.F_OK);
  },

  collider: function (args) {
    args = args || [];
    return spawnSync('collider', args, { cwd: this.tmpLocation });
  },
};

module.exports = TempDir;

<|edits_diff|>
--- test/lib/TempDir.js
+++ test/lib/TempDir.js
@@ -2,6 +2,8 @@
 //

 'use strict';
+
+var cmds = require('../../lib/commands');

 var fs = require('fs');
 var path = require('path');
<|current_version|>
// test/lib/TempDir.js
//

'use strict';

var cmds = require('../../lib/commands');

var fs = require('fs');
var path = require('path');
var spawnSync = require('child_process').spawnSync;

var mkdirp = require('mkdirp');
var rimraf = require('rimraf');

var TempDir = {
  tmpLocation: null,

  prepare: function () {
    mkdirp.sync(this.tmpLocation);
  },

  clean: function () {
    rimraf.sync(this.tmpLocation);
  },

  getPath: function (name) {
    return path.join(this.tmpLocation, name);
  },

  read: function (name) {
    return fs.readFileSync(this.getPath(name), 'utf8');
  },

  readJson: function (name) {
    return JSON.parse(this.read(name));
  },

  exists: function (name) {
    return fs.accessSync(path.join(this.tmpLocation, name), fs.F_OK);
  },

  collider: function (args) {
    args = args || [];
    return spawnSync('collider', args, { cwd: this.tmpLocation });
  },
};

module.exports = TempDir;

<|next_version|>
// test/lib/TempDir.js
//

'use strict';

var cmds = require('../../lib/commands');

var fs = require('fs');
var path = require('path');
var spawnSync = require('child_process').spawnSync;

var mkdirp = require('mkdirp');
var rimraf = require('rimraf');

var TempDir = {
  tmpLocation: null,

  prepare: function () {
    mkdirp.sync(this.tmpLocation);
  },

  clean: function () {
    rimraf.sync(this.tmpLocation);
  },

  getPath: function (name) {
    return path.join(this.tmpLocation, name);
  },

  read: function (name) {
    return fs.readFileSync(this.getPath(name), 'utf8');
  },

  readJson: function (name) {
    return JSON.parse(this.read(name));
  },

  exists: function (name) {
    return fs.accessSync(path.join(this.tmpLocation, name), fs.F_OK);
  },

  runCmd: function (cmd, argv) {
    argv = [cmd].concat(argv);
    return cmds[cmd](argv, this.tmpLocation);
  },

  collider: function (args) {
    args = args || [];
    return spawnSync('collider', args, { cwd: this.tmpLocation });
  },
};

module.exports = TempDir;


Solve a bug on code visitor

[zmarkdown] Solve a bug on code visitor

--- packages/zmarkdown/postprocessors/html-wrap-code.js
+++ packages/zmarkdown/postprocessors/html-wrap-code.js
@@ -20,7 +20,11 @@

       if (preElem.length === 1) {
         const classes = preElem[0].children[0].properties.className
+        if (!classes) return
+
         const langClass = classes.filter(c => c.startsWith('language'))
+        if (langClass.length === 0 || langClass[0].length < 9) return
+
         const language = langClass[0].substring(9)

         // rewrite class name

<|original_code|>
/**
 * This plugin adds a class precising the language
 * to code blocks (.hljs-code-div).
 */

const visit = require('unist-util-visit')

module.exports = () => (tree) => {
  visit(tree, (node) => {
    // filter super code blocks
    if (node.type === 'element' &&
        node.tagName === 'div' &&
        node.properties &&
        node.properties.className &&
        node.properties.className.includes('hljs-code-div')
    ) {
      // get pre > code
      const preElem = node.children.filter(c =>
        c.type === 'element' && c.tagName === 'pre')

      if (preElem.length === 1) {
        const classes = preElem[0].children[0].properties.className
        const langClass = classes.filter(c => c.startsWith('language'))
        const language = langClass[0].substring(9)

        // rewrite class name
        if (langClass.length === 1 && language !== 'div') {
          node.properties.className.push(`hljs-code-${language}`)
        }
      }
    }
  })
}

<|edits_diff|>
--- packages/zmarkdown/postprocessors/html-wrap-code.js
+++ packages/zmarkdown/postprocessors/html-wrap-code.js
@@ -20,6 +20,8 @@

       if (preElem.length === 1) {
         const classes = preElem[0].children[0].properties.className
+        if (!classes) return
+
         const langClass = classes.filter(c => c.startsWith('language'))
         const language = langClass[0].substring(9)

<|current_version|>
/**
 * This plugin adds a class precising the language
 * to code blocks (.hljs-code-div).
 */

const visit = require('unist-util-visit')

module.exports = () => (tree) => {
  visit(tree, (node) => {
    // filter super code blocks
    if (node.type === 'element' &&
        node.tagName === 'div' &&
        node.properties &&
        node.properties.className &&
        node.properties.className.includes('hljs-code-div')
    ) {
      // get pre > code
      const preElem = node.children.filter(c =>
        c.type === 'element' && c.tagName === 'pre')

      if (preElem.length === 1) {
        const classes = preElem[0].children[0].properties.className
        if (!classes) return

        const langClass = classes.filter(c => c.startsWith('language'))
        const language = langClass[0].substring(9)

        // rewrite class name
        if (langClass.length === 1 && language !== 'div') {
          node.properties.className.push(`hljs-code-${language}`)
        }
      }
    }
  })
}

<|next_version|>
/**
 * This plugin adds a class precising the language
 * to code blocks (.hljs-code-div).
 */

const visit = require('unist-util-visit')

module.exports = () => (tree) => {
  visit(tree, (node) => {
    // filter super code blocks
    if (node.type === 'element' &&
        node.tagName === 'div' &&
        node.properties &&
        node.properties.className &&
        node.properties.className.includes('hljs-code-div')
    ) {
      // get pre > code
      const preElem = node.children.filter(c =>
        c.type === 'element' && c.tagName === 'pre')

      if (preElem.length === 1) {
        const classes = preElem[0].children[0].properties.className
        if (!classes) return

        const langClass = classes.filter(c => c.startsWith('language'))
        if (langClass.length === 0 || langClass[0].length < 9) return

        const language = langClass[0].substring(9)

        // rewrite class name
        if (langClass.length === 1 && language !== 'div') {
          node.properties.className.push(`hljs-code-${language}`)
        }
      }
    }
  })
}


Add proper exporting of programStrings

Add proper exporting of programStrings

--- src/localization/utilities.js
+++ src/localization/utilities.js
@@ -11,6 +11,7 @@
 import { pageInfoStrings } from './pageInfoStrings';
 import { syncStrings } from './syncStrings';
 import { tableStrings } from './tableStrings';
+import { programStrings } from './programStrings';

 export function setCurrentLanguage(language) {
   authStrings.setLanguage(language);
@@ -21,6 +22,7 @@
   pageInfoStrings.setLanguage(language);
   tableStrings.setLanguage(language);
   syncStrings.setLanguage(language);
+  programStrings.setLanguage(language);
 }

 export default setCurrentLanguage;

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

import { authStrings } from './authStrings';
import { buttonStrings } from './buttonStrings';
import { generalStrings } from './generalStrings';
import { modalStrings } from './modalStrings';
import { navStrings } from './navStrings';
import { pageInfoStrings } from './pageInfoStrings';
import { syncStrings } from './syncStrings';
import { tableStrings } from './tableStrings';

export function setCurrentLanguage(language) {
  authStrings.setLanguage(language);
  buttonStrings.setLanguage(language);
  generalStrings.setLanguage(language);
  modalStrings.setLanguage(language);
  navStrings.setLanguage(language);
  pageInfoStrings.setLanguage(language);
  tableStrings.setLanguage(language);
  syncStrings.setLanguage(language);
}

export default setCurrentLanguage;

<|edits_diff|>
--- src/localization/utilities.js
+++ src/localization/utilities.js
@@ -11,6 +11,7 @@
 import { pageInfoStrings } from './pageInfoStrings';
 import { syncStrings } from './syncStrings';
 import { tableStrings } from './tableStrings';
+import { programStrings } from './programStrings';

 export function setCurrentLanguage(language) {
   authStrings.setLanguage(language);
<|current_version|>
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import { authStrings } from './authStrings';
import { buttonStrings } from './buttonStrings';
import { generalStrings } from './generalStrings';
import { modalStrings } from './modalStrings';
import { navStrings } from './navStrings';
import { pageInfoStrings } from './pageInfoStrings';
import { syncStrings } from './syncStrings';
import { tableStrings } from './tableStrings';
import { programStrings } from './programStrings';

export function setCurrentLanguage(language) {
  authStrings.setLanguage(language);
  buttonStrings.setLanguage(language);
  generalStrings.setLanguage(language);
  modalStrings.setLanguage(language);
  navStrings.setLanguage(language);
  pageInfoStrings.setLanguage(language);
  tableStrings.setLanguage(language);
  syncStrings.setLanguage(language);
}

export default setCurrentLanguage;

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

import { authStrings } from './authStrings';
import { buttonStrings } from './buttonStrings';
import { generalStrings } from './generalStrings';
import { modalStrings } from './modalStrings';
import { navStrings } from './navStrings';
import { pageInfoStrings } from './pageInfoStrings';
import { syncStrings } from './syncStrings';
import { tableStrings } from './tableStrings';
import { programStrings } from './programStrings';

export function setCurrentLanguage(language) {
  authStrings.setLanguage(language);
  buttonStrings.setLanguage(language);
  generalStrings.setLanguage(language);
  modalStrings.setLanguage(language);
  navStrings.setLanguage(language);
  pageInfoStrings.setLanguage(language);
  tableStrings.setLanguage(language);
  syncStrings.setLanguage(language);
  programStrings.setLanguage(language);
}

export default setCurrentLanguage;


Fix issue where errors aren't being propagated.

--- packages/truffle-core/lib/deferredchain.js
+++ packages/truffle-core/lib/deferredchain.js
@@ -7,6 +7,7 @@

   this.await = new Promise(function() {
     self._done = arguments[0];
+    self._error = arguments[1];
   });
   this.started = false;
 };
@@ -17,6 +18,9 @@
     var args = Array.prototype.slice.call(arguments);

     return fn.apply(null, args);
+  });
+  this.chain = this.chain.catch(function(e) {
+    self._error(e);
   });

   return this;

<|original_code|>
function DeferredChain() {
  var self = this;
  this.chain = new Promise(function(accept, reject) {
    self._accept = accept;
    self._reject = reject;
  });

  this.await = new Promise(function() {
    self._done = arguments[0];
  });
  this.started = false;
};

DeferredChain.prototype.then = function(fn) {
  var self = this;
  this.chain = this.chain.then(function() {
    var args = Array.prototype.slice.call(arguments);

    return fn.apply(null, args);
  });

  return this;
};

DeferredChain.prototype.catch = function(fn) {
  var self = this;
  this.chain = this.chain.catch(function() {
    var args = Array.prototype.slice.call(arguments);

    return fn.apply(null, args);
  });

  return this;
};

DeferredChain.prototype.start = function() {
  this.started = true;
  this.chain = this.chain.then(this._done);
  this._accept();
  return this.await;
};

module.exports = DeferredChain;

<|edits_diff|>
--- packages/truffle-core/lib/deferredchain.js
+++ packages/truffle-core/lib/deferredchain.js
@@ -7,6 +7,7 @@

   this.await = new Promise(function() {
     self._done = arguments[0];
+    self._error = arguments[1];
   });
   this.started = false;
 };
<|current_version|>
function DeferredChain() {
  var self = this;
  this.chain = new Promise(function(accept, reject) {
    self._accept = accept;
    self._reject = reject;
  });

  this.await = new Promise(function() {
    self._done = arguments[0];
    self._error = arguments[1];
  });
  this.started = false;
};

DeferredChain.prototype.then = function(fn) {
  var self = this;
  this.chain = this.chain.then(function() {
    var args = Array.prototype.slice.call(arguments);

    return fn.apply(null, args);
  });

  return this;
};

DeferredChain.prototype.catch = function(fn) {
  var self = this;
  this.chain = this.chain.catch(function() {
    var args = Array.prototype.slice.call(arguments);

    return fn.apply(null, args);
  });

  return this;
};

DeferredChain.prototype.start = function() {
  this.started = true;
  this.chain = this.chain.then(this._done);
  this._accept();
  return this.await;
};

module.exports = DeferredChain;

<|next_version|>
function DeferredChain() {
  var self = this;
  this.chain = new Promise(function(accept, reject) {
    self._accept = accept;
    self._reject = reject;
  });

  this.await = new Promise(function() {
    self._done = arguments[0];
    self._error = arguments[1];
  });
  this.started = false;
};

DeferredChain.prototype.then = function(fn) {
  var self = this;
  this.chain = this.chain.then(function() {
    var args = Array.prototype.slice.call(arguments);

    return fn.apply(null, args);
  });
  this.chain = this.chain.catch(function(e) {
    self._error(e);
  });

  return this;
};

DeferredChain.prototype.catch = function(fn) {
  var self = this;
  this.chain = this.chain.catch(function() {
    var args = Array.prototype.slice.call(arguments);

    return fn.apply(null, args);
  });

  return this;
};

DeferredChain.prototype.start = function() {
  this.started = true;
  this.chain = this.chain.then(this._done);
  this._accept();
  return this.await;
};

module.exports = DeferredChain;


Update map and list when marker is pressed

--- component/BackgroundMap.js
+++ component/BackgroundMap.js
@@ -4,6 +4,7 @@
 import { StyleSheet } from 'react-native'

 import { updateMap } from '../store/reducer/map'
+import merge from '../util/merge'

 // This slightly strange structure (taking the first element out of the array)
@@ -30,6 +31,7 @@
           b.id !== props.selected ?
             <MapView.Marker key={b.shortDisplay}
                 coordinate={b.address.location}
+                onPress={() => props.updateMap(merge(props.mapPosition, b.address.location))}
             /> : undefined
       )}
     </MapView>

<|original_code|>
import React from 'react'
import MapView from 'react-native-maps'
import { connect } from 'react-redux'
import { StyleSheet } from 'react-native'

import { updateMap } from '../store/reducer/map'

// This slightly strange structure (taking the first element out of the array)
// is to avoid pins getting 'stuck' on blue (yes this really happens!)

const renderClosestMarker = (business, selected) => {
  if (!selected) {
    return undefined
  }
  const selectedBusinessAddress = business.find(b => b.id === selected).address
  if (!selectedBusinessAddress) {
    return undefined
  }
  return <MapView.Marker coordinate={selectedBusinessAddress.location} pinColor={'blue'}/>
}

const BackgroundMap = (props) =>
  <MapView style={{...StyleSheet.absoluteFillObject}}
        region={props.mapPosition}
        onRegionChange={props.updateMap}>
      {renderClosestMarker(props.business, props.selected)}
      {props.business.filter(b => b.address)
        .map(b =>
          b.id !== props.selected ?
            <MapView.Marker key={b.shortDisplay}
                coordinate={b.address.location}
            /> : undefined
      )}
    </MapView>

const mapStateToProps = (state) => ({
  ...state.business,
  mapPosition: state.map
})

const mapDispatchToProps = (dispatch) => ({
  updateMap: (params) => dispatch(updateMap(params))
})

export default connect(mapStateToProps, mapDispatchToProps)(BackgroundMap)

<|edits_diff|>
--- component/BackgroundMap.js
+++ component/BackgroundMap.js
@@ -4,6 +4,7 @@
 import { StyleSheet } from 'react-native'

 import { updateMap } from '../store/reducer/map'
+import merge from '../util/merge'

 // This slightly strange structure (taking the first element out of the array)
<|current_version|>
import React from 'react'
import MapView from 'react-native-maps'
import { connect } from 'react-redux'
import { StyleSheet } from 'react-native'

import { updateMap } from '../store/reducer/map'
import merge from '../util/merge'

// This slightly strange structure (taking the first element out of the array)
// is to avoid pins getting 'stuck' on blue (yes this really happens!)

const renderClosestMarker = (business, selected) => {
  if (!selected) {
    return undefined
  }
  const selectedBusinessAddress = business.find(b => b.id === selected).address
  if (!selectedBusinessAddress) {
    return undefined
  }
  return <MapView.Marker coordinate={selectedBusinessAddress.location} pinColor={'blue'}/>
}

const BackgroundMap = (props) =>
  <MapView style={{...StyleSheet.absoluteFillObject}}
        region={props.mapPosition}
        onRegionChange={props.updateMap}>
      {renderClosestMarker(props.business, props.selected)}
      {props.business.filter(b => b.address)
        .map(b =>
          b.id !== props.selected ?
            <MapView.Marker key={b.shortDisplay}
                coordinate={b.address.location}
            /> : undefined
      )}
    </MapView>

const mapStateToProps = (state) => ({
  ...state.business,
  mapPosition: state.map
})

const mapDispatchToProps = (dispatch) => ({
  updateMap: (params) => dispatch(updateMap(params))
})

export default connect(mapStateToProps, mapDispatchToProps)(BackgroundMap)

<|next_version|>
import React from 'react'
import MapView from 'react-native-maps'
import { connect } from 'react-redux'
import { StyleSheet } from 'react-native'

import { updateMap } from '../store/reducer/map'
import merge from '../util/merge'

// This slightly strange structure (taking the first element out of the array)
// is to avoid pins getting 'stuck' on blue (yes this really happens!)

const renderClosestMarker = (business, selected) => {
  if (!selected) {
    return undefined
  }
  const selectedBusinessAddress = business.find(b => b.id === selected).address
  if (!selectedBusinessAddress) {
    return undefined
  }
  return <MapView.Marker coordinate={selectedBusinessAddress.location} pinColor={'blue'}/>
}

const BackgroundMap = (props) =>
  <MapView style={{...StyleSheet.absoluteFillObject}}
        region={props.mapPosition}
        onRegionChange={props.updateMap}>
      {renderClosestMarker(props.business, props.selected)}
      {props.business.filter(b => b.address)
        .map(b =>
          b.id !== props.selected ?
            <MapView.Marker key={b.shortDisplay}
                coordinate={b.address.location}
                onPress={() => props.updateMap(merge(props.mapPosition, b.address.location))}
            /> : undefined
      )}
    </MapView>

const mapStateToProps = (state) => ({
  ...state.business,
  mapPosition: state.map
})

const mapDispatchToProps = (dispatch) => ({
  updateMap: (params) => dispatch(updateMap(params))
})

export default connect(mapStateToProps, mapDispatchToProps)(BackgroundMap)


Return a 500 on exit. Also add a bit of console logging.

--- index.js
+++ index.js
@@ -3,8 +3,11 @@

 app.get('/', function(req, res) {
   if (new Date().getTime() % 3 === 0) {
+    console.log('catastrophic failure!');
+    res.status(500).send('oh no!');
     process.exit(1);
   } else {
+    console.log('incredible success!');
     res.send('hello world');
   }
 });

<|original_code|>
var express = require('express');
var app = express();

app.get('/', function(req, res) {
  if (new Date().getTime() % 3 === 0) {
    process.exit(1);
  } else {
    res.send('hello world');
  }
});

var server = app.listen(process.env.BAD_WEBAPP_PORT, function() {
  var host = server.address().address;
  var port = server.address().port;

  console.log('Bad web app listening at http://%s:%s', host, port);
});

<|edits_diff|>
--- index.js
+++ index.js
@@ -3,6 +3,8 @@

 app.get('/', function(req, res) {
   if (new Date().getTime() % 3 === 0) {
+    console.log('catastrophic failure!');
+    res.status(500).send('oh no!');
     process.exit(1);
   } else {
     res.send('hello world');
<|current_version|>
var express = require('express');
var app = express();

app.get('/', function(req, res) {
  if (new Date().getTime() % 3 === 0) {
    console.log('catastrophic failure!');
    res.status(500).send('oh no!');
    process.exit(1);
  } else {
    res.send('hello world');
  }
});

var server = app.listen(process.env.BAD_WEBAPP_PORT, function() {
  var host = server.address().address;
  var port = server.address().port;

  console.log('Bad web app listening at http://%s:%s', host, port);
});

<|next_version|>
var express = require('express');
var app = express();

app.get('/', function(req, res) {
  if (new Date().getTime() % 3 === 0) {
    console.log('catastrophic failure!');
    res.status(500).send('oh no!');
    process.exit(1);
  } else {
    console.log('incredible success!');
    res.send('hello world');
  }
});

var server = app.listen(process.env.BAD_WEBAPP_PORT, function() {
  var host = server.address().address;
  var port = server.address().port;

  console.log('Bad web app listening at http://%s:%s', host, port);
});