<commit_before>'use strict';

var test = require('tape')

var InputMask = require('../lib')

test('formatValueToPattern', function(t) {
  t.plan(7)

  function formatValueToPattern(value, pattern) {
    return new InputMask.Pattern(pattern).formatValue(value.split('')).join('')
  }

  t.equal(formatValueToPattern('', '11 11'), '__ __', 'Empty value gets all placeholders')
  t.equal(formatValueToPattern('1', '11 11'), '1_ __', 'Partial value 1')
  t.equal(formatValueToPattern('12', '11 11'), '12 __', 'Partial value 2')
  t.equal(formatValueToPattern('123', '11 11'), '12 3_', 'Partial value 3')
  t.equal(formatValueToPattern('1234', '11 11'), '12 34', 'Complete value (values only)')
  t.equal(formatValueToPattern('12 34', '11 11'), '12 34', 'Complete value (with format characters)')

  t.equal(formatValueToPattern('', '11/11/1111'), '__/__/____', 'Empty value gets all placeholders')
})

test('Constructor options', function(t) {
  t.plan(6)

  t.throws(function() { new InputMask },
           /InputMask: you must provide a pattern./,
           'Pattern is required')
  t.throws(function() { new InputMask({pattern: '------'}) },
           /InputMask: pattern "------" does not contain any editable characters./,
           'Patterns must contain editable characters')

  var mask = new InputMask({pattern: '1111 1111 1111 1111'})
  t.equal(mask.pattern.firstEditableIndex, 0, 'Full range first editable index ')
  t.equal(mask.pattern.lastEditableIndex, 18, 'Full range last editable index calculation')

  mask = new InputMask({pattern: '---111---'})
  t.equal(mask.pattern.firstEditableIndex, 3, 'Partial range first editable index calculation')
  t.equal(mask.pattern.lastEditableIndex, 5, 'Partial range last editable index calculation')
})

test('Placeholder characters', function(t) {
  t.plan(1)
  var mask = new InputMask({pattern: '1A**', value: '9f9f'})
  t.equal(mask.getValue(), '9f9f', 'All values, no placeholders')
})

test('Escaping placeholder characters', function(t) {
  //t.plan(2)
  t.throws(function() { new InputMask({pattern: '\\1\\A\\*\\*'}) },
           /InputMask: pattern "\\1\\A\\\*\\\*" does not contain any editable characters./,
           'Escaped placeholders treated as static characters')
  t.throws(function() { new InputMask({pattern: 'A1*\\'}) },
           /InputMask: pattern ends with a raw \\/,
           'A pattern must not end with an escape character')
  var mask = new InputMask({pattern: '\\\\\\1:1,\\A:A,\\*:*\\\\', value: '9Zg'})
  t.equal(mask.getValue(), '\\1:9,A:Z,*:g\\', 'Escape character can be escaped')
  t.end()
})

test('Basic input', function(t) {
  t.plan(23)

  var mask = new InputMask({
    pattern: '1111 1111 1111 1111'
  })
  t.equal(mask.getValue(), '____ ____ ____ ____', 'Initial mask value is blank')
  t.false(mask.input('a'), 'Invalid input ignored')

  // Input is provided to the mask one character at a time
  t.true(mask.input('1'), 'Valid input accepted')
  t.true(mask.input('2'), 'Valid input accepted')
  t.true(mask.input('3'), 'Valid input accepted')
  t.true(mask.input('4'), 'Valid input accepted')
  t.deepEqual(mask.selection, {start: 5, end: 5}, 'Skipped over blank')
  t.true(mask.input('1'), 'Valid input accepted')
  t.true(mask.input('2'), 'Valid input accepted')
  t.true(mask.input('3'), 'Valid input accepted')
  t.true(mask.input('4'), 'Valid input accepted')
  t.deepEqual(mask.selection, {start: 10, end: 10}, 'Skipped over blank')
  t.true(mask.input('1'), 'Valid input accepted')
  t.true(mask.input('2'), 'Valid input accepted')
  t.true(mask.input('3'), 'Valid input accepted')
  t.true(mask.input('4'), 'Valid input accepted')
  t.deepEqual(mask.selection, {start: 15, end: 15}, 'Skipped over blank')
  t.true(mask.input('1'), 'Valid input accepted')
  t.true(mask.input('2'), 'Valid input accepted')
  t.true(mask.input('3'), 'Valid input accepted')
  t.true(mask.input('4'), 'Valid input accepted')
  t.false(mask.input('1'), 'Input ignored when cursor is at the end of the pattern')
  t.equal(mask.getValue(), '1234 1234 1234 1234', 'Final value')
})

test('Input with selected range', function(t) {
  t.plan(11)

  var mask = new InputMask({
    pattern: '1111 1111 1111 1111',
    value: '1234123412341234'
  })
  t.equal(mask.getValue(), '1234 1234 1234 1234', 'Initial mask value is formatted')

  // If you have text selected, input will set the first selected character and
  // clear the rest.
  mask.selection = {start: 6, end: 8}
  t.true(mask.input('9'), 'Valid input accepted')
  t.equal(mask.getValue(), '1234 19_4 1234 1234', 'Other selected characters are blanked out')
  t.deepEqual(mask.selection, {start: 7, end: 7}, 'Curspr placed after first character in selection')
  t.true(mask.input('8'), 'Valid input accepted')
  t.equal(mask.getValue(), '1234 1984 1234 1234', 'Final value')

  // Static parts of the pattern will be respected even if they're part of a
  // selection being cleared when input is given.
  mask.selection = {start: 6, end: 12}
  t.true(mask.input('2'), 'Valid input accepted')
  t.equal(mask.getValue(), '1234 12__ __34 1234', 'Only blanks out input positions')

  // If a range of charactes are selected and the first character was contained
  // in the range, any input given should apply to it.
  mask = new InputMask({pattern: '----- 1111 1111'})
  mask.selection = {start: 0, end: 15}
  t.true(mask.input('2'), 'Valid input accepted')
  t.equal(mask.getValue(), '----- 2___ ____', 'Value was applied to the first editable character')
  t.deepEqual(mask.selection, {start: 7, end: 7}, 'Cursor was placed after first editable character')
})

test('Skipping multiple static characters', function(t) {
  t.plan(3)

  // When taking input, the cursor will jump to the next available character
  // which takes input, regardless of how many subsequent static pattern parts
  // there are.
  var mask = new InputMask({
    pattern: '1----1'
  })
  t.true(mask.input('1'), 'Valid input accepted')
  t.true(mask.input('2'), 'Valid input accepted')
  t.equal(mask.getValue(), '1----2', 'Final value')
})

test('Basic backspacing', function(t) {
  t.plan(24)

  var mask = new InputMask({
    pattern: '1111 1111 1111 1111',
    value: '1234123412341234'
  })
  t.false(mask.backspace(), 'Backspace with cursor at start of input is ignored')
  mask.selection = {start: 19, end: 19}
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Valid backspace accepted')
  // Backspacking doesn't automatically skip characters, as we can't tell when
  // the user intends to start making input again, so it just steps over static
  // parts of the mask when you backspace with the cursor ahead of them.
  t.true(mask.backspace(), 'Skipped over blank')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.equal(mask.getValue(), '1234 1234 ____ ____', 'Intermediate value')
  t.deepEqual(mask.selection, {start: 10, end: 10}, 'Cursor remains in front of last deleted character')
  t.true(mask.backspace(), 'Skipped over blank')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Skipped over blank')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.equal(mask.getValue(), '____ ____ ____ ____', 'Final value')
  t.deepEqual(mask.selection, {start: 0, end: 0}, 'Cursor ended up at the start of input')
})

test('Backspace with selected range', function(t) {
  t.plan(4)

  var mask = new InputMask({
    pattern: '1111 1111 1111 1111',
    value: '1234123412341234'
  })
  t.equal(mask.getValue(), '1234 1234 1234 1234', 'Initial mask value is formatted')
  mask.selection = {start: 6, end: 8}
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.equal(mask.getValue(), '1234 1__4 1234 1234', 'Other selected characters are blanked out')
  t.deepEqual(mask.selection, {start: 6, end: 6}, 'Cursor placed before first character in selection')
})

test('Pasting', function(t) {
  t.plan(10)

  var mask = new InputMask({
    pattern: '1111 1111 1111 1111'
  })

  // Invalid characters at any position will cause a paste to be rejected
  t.false(mask.paste('1234123A12341234'), 'Invalid input rejected')

  // A paste larger than the available remaining space will not be rejected if
  // input was valid up to the end of the editable portion of the mask.
  t.true(mask.paste('12341234123412349'), 'Too large input not rejected')
  t.equal(mask.getValue(), '1234 1234 1234 1234')

  // Pasted input doesn't have to contain static formatting characters...
  mask.setValue('')
  mask.selection = {start: 0, end: 0}
  t.true(mask.paste('1234123412341234'), 'Complete, valid input accepted')
  t.equal(mask.getValue(), '1234 1234 1234 1234', 'Formatted pasted value')
  t.deepEqual(mask.selection, {start: 19, end: 19}, 'Cursor position after paste')

  mask.selection = {start: 0, end: 19}
  t.true(mask.backspace(), 'Backspace to delete content')
  t.equal(mask.getValue(), '____ ____ ____ ____', 'Empty after backspace')

  // Pasted input can contain static formatting characters
  t.true(mask.paste('1234 1234 1234 1234'), 'Pasted value can contain static parts')
  t.equal(mask.getValue(), '1234 1234 1234 1234', 'Value after paste')
})

test('Pasting with leading static pattern in selection', function(t) {
  t.plan(7)

  var mask = new InputMask({
    pattern: '----- 1111 1111'
  })

  // Pasting with cursor in leading static part
  mask.selection = {start: 0, end: 0}
  t.false(mask.paste('1234 5678'), 'Paste fails if leading static pattern not matched')
  t.ok(mask.paste('----- 1234 5678'), 'Paste succeeds when leading static pattern is matched')
  t.equal(mask.getValue(), '----- 1234 5678')

  // Pasting with selection including leading static part
  mask.setValue('')
  t.equal(mask.getValue(), '----- ____ ____')
  mask.selection = {start: 0, end: 15}
  t.false(mask.paste('1'), 'Paste fails if leading static pattern not matched')
  t.ok(mask.paste('----- 1'), 'Paste succeeds when leading static pattern is matched')
  t.equal(mask.getValue(), '----- 1___ ____')
})

test('Setting selection', function(t) {
  t.plan(8)

  var mask = new InputMask({pattern: '----- [[11 1111 ]]-'})
  t.equal(mask.pattern.firstEditableIndex, 8, 'First editable index calculation')
  t.equal(mask.pattern.lastEditableIndex, 14, 'Last editable index calculation')
  // The cursor cannot be placed before the first editable index...
  t.true(mask.setSelection({start: 0, end: 0}), 'Cursor before editable region is changed')
  t.deepEqual(mask.selection, {start: 8, end: 8}, 'Cursor placed at first editable character')
  // ...or beyond the last editable character
  t.true(mask.setSelection({start: 18, end: 18}), 'Cursor after editable region is changed')
  t.deepEqual(mask.selection, {start: 15, end: 15}, 'Cursor placed after last editable character')
  // ...however a selection can span beyond the editable region
  t.false(mask.setSelection({start: 0, end: 19}), 'Selection beyond editable region not changed')
  t.deepEqual(mask.selection, {start: 0, end: 19}, 'Whole value can be selected')
})<commit_msg>Added some more constructor tests<commit_after>'use strict';

var test = require('tape')

var InputMask = require('../lib')

test('formatValueToPattern', function(t) {
  t.plan(7)

  function formatValueToPattern(value, pattern) {
    return new InputMask.Pattern(pattern).formatValue(value.split('')).join('')
  }

  t.equal(formatValueToPattern('', '11 11'), '__ __', 'Empty value gets all placeholders')
  t.equal(formatValueToPattern('1', '11 11'), '1_ __', 'Partial value 1')
  t.equal(formatValueToPattern('12', '11 11'), '12 __', 'Partial value 2')
  t.equal(formatValueToPattern('123', '11 11'), '12 3_', 'Partial value 3')
  t.equal(formatValueToPattern('1234', '11 11'), '12 34', 'Complete value (values only)')
  t.equal(formatValueToPattern('12 34', '11 11'), '12 34', 'Complete value (with format characters)')

  t.equal(formatValueToPattern('', '11/11/1111'), '__/__/____', 'Empty value gets all placeholders')
})

test('Constructor options', function(t) {
  t.plan(8)

  t.throws(function() { new InputMask },
           /InputMask: you must provide a pattern./,
           'Pattern is required')
  t.throws(function() { new InputMask({pattern: '------'}) },
           /InputMask: pattern "------" does not contain any editable characters./,
           'Patterns must contain editable characters')

  var mask = new InputMask({pattern: '1111 1111 1111 1111'})
  t.equal(mask.pattern.firstEditableIndex, 0, 'Full range first editable index ')
  t.equal(mask.pattern.lastEditableIndex, 18, 'Full range last editable index calculation')

  mask = new InputMask({pattern: '---111---'})
  t.equal(mask.pattern.firstEditableIndex, 3, 'Partial range first editable index calculation')
  t.equal(mask.pattern.lastEditableIndex, 5, 'Partial range last editable index calculation')
  t.deepEqual(mask.selection, {start: 3, end: 3}, 'Default selection goes to first editable character')

  mask = new InputMask({pattern: '-1-1-1-', value: '987'})
  t.equal(mask.getValue(), '-9-8-7-', 'Initial value is formatted')
})

test('Placeholder characters', function(t) {
  t.plan(1)
  var mask = new InputMask({pattern: '1A**', value: '9f9f'})
  t.equal(mask.getValue(), '9f9f', 'All values, no placeholders')
})

test('Escaping placeholder characters', function(t) {
  //t.plan(2)
  t.throws(function() { new InputMask({pattern: '\\1\\A\\*\\*'}) },
           /InputMask: pattern "\\1\\A\\\*\\\*" does not contain any editable characters./,
           'Escaped placeholders treated as static characters')
  t.throws(function() { new InputMask({pattern: 'A1*\\'}) },
           /InputMask: pattern ends with a raw \\/,
           'A pattern must not end with an escape character')
  var mask = new InputMask({pattern: '\\\\\\1:1,\\A:A,\\*:*\\\\', value: '9Zg'})
  t.equal(mask.getValue(), '\\1:9,A:Z,*:g\\', 'Escape character can be escaped')
  t.end()
})

test('Basic input', function(t) {
  t.plan(23)

  var mask = new InputMask({
    pattern: '1111 1111 1111 1111'
  })
  t.equal(mask.getValue(), '____ ____ ____ ____', 'Initial mask value is blank')
  t.false(mask.input('a'), 'Invalid input ignored')

  // Input is provided to the mask one character at a time
  t.true(mask.input('1'), 'Valid input accepted')
  t.true(mask.input('2'), 'Valid input accepted')
  t.true(mask.input('3'), 'Valid input accepted')
  t.true(mask.input('4'), 'Valid input accepted')
  t.deepEqual(mask.selection, {start: 5, end: 5}, 'Skipped over blank')
  t.true(mask.input('1'), 'Valid input accepted')
  t.true(mask.input('2'), 'Valid input accepted')
  t.true(mask.input('3'), 'Valid input accepted')
  t.true(mask.input('4'), 'Valid input accepted')
  t.deepEqual(mask.selection, {start: 10, end: 10}, 'Skipped over blank')
  t.true(mask.input('1'), 'Valid input accepted')
  t.true(mask.input('2'), 'Valid input accepted')
  t.true(mask.input('3'), 'Valid input accepted')
  t.true(mask.input('4'), 'Valid input accepted')
  t.deepEqual(mask.selection, {start: 15, end: 15}, 'Skipped over blank')
  t.true(mask.input('1'), 'Valid input accepted')
  t.true(mask.input('2'), 'Valid input accepted')
  t.true(mask.input('3'), 'Valid input accepted')
  t.true(mask.input('4'), 'Valid input accepted')
  t.false(mask.input('1'), 'Input ignored when cursor is at the end of the pattern')
  t.equal(mask.getValue(), '1234 1234 1234 1234', 'Final value')
})

test('Input with selected range', function(t) {
  t.plan(11)

  var mask = new InputMask({
    pattern: '1111 1111 1111 1111',
    value: '1234123412341234'
  })
  t.equal(mask.getValue(), '1234 1234 1234 1234', 'Initial mask value is formatted')

  // If you have text selected, input will set the first selected character and
  // clear the rest.
  mask.selection = {start: 6, end: 8}
  t.true(mask.input('9'), 'Valid input accepted')
  t.equal(mask.getValue(), '1234 19_4 1234 1234', 'Other selected characters are blanked out')
  t.deepEqual(mask.selection, {start: 7, end: 7}, 'Curspr placed after first character in selection')
  t.true(mask.input('8'), 'Valid input accepted')
  t.equal(mask.getValue(), '1234 1984 1234 1234', 'Final value')

  // Static parts of the pattern will be respected even if they're part of a
  // selection being cleared when input is given.
  mask.selection = {start: 6, end: 12}
  t.true(mask.input('2'), 'Valid input accepted')
  t.equal(mask.getValue(), '1234 12__ __34 1234', 'Only blanks out input positions')

  // If a range of charactes are selected and the first character was contained
  // in the range, any input given should apply to it.
  mask = new InputMask({pattern: '----- 1111 1111'})
  mask.selection = {start: 0, end: 15}
  t.true(mask.input('2'), 'Valid input accepted')
  t.equal(mask.getValue(), '----- 2___ ____', 'Value was applied to the first editable character')
  t.deepEqual(mask.selection, {start: 7, end: 7}, 'Cursor was placed after first editable character')
})

test('Skipping multiple static characters', function(t) {
  t.plan(3)

  // When taking input, the cursor will jump to the next available character
  // which takes input, regardless of how many subsequent static pattern parts
  // there are.
  var mask = new InputMask({
    pattern: '1----1'
  })
  t.true(mask.input('1'), 'Valid input accepted')
  t.true(mask.input('2'), 'Valid input accepted')
  t.equal(mask.getValue(), '1----2', 'Final value')
})

test('Basic backspacing', function(t) {
  t.plan(24)

  var mask = new InputMask({
    pattern: '1111 1111 1111 1111',
    value: '1234123412341234'
  })
  t.false(mask.backspace(), 'Backspace with cursor at start of input is ignored')
  mask.selection = {start: 19, end: 19}
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Valid backspace accepted')
  // Backspacking doesn't automatically skip characters, as we can't tell when
  // the user intends to start making input again, so it just steps over static
  // parts of the mask when you backspace with the cursor ahead of them.
  t.true(mask.backspace(), 'Skipped over blank')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.equal(mask.getValue(), '1234 1234 ____ ____', 'Intermediate value')
  t.deepEqual(mask.selection, {start: 10, end: 10}, 'Cursor remains in front of last deleted character')
  t.true(mask.backspace(), 'Skipped over blank')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Skipped over blank')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.equal(mask.getValue(), '____ ____ ____ ____', 'Final value')
  t.deepEqual(mask.selection, {start: 0, end: 0}, 'Cursor ended up at the start of input')
})

test('Backspace with selected range', function(t) {
  t.plan(4)

  var mask = new InputMask({
    pattern: '1111 1111 1111 1111',
    value: '1234123412341234'
  })
  t.equal(mask.getValue(), '1234 1234 1234 1234', 'Initial mask value is formatted')
  mask.selection = {start: 6, end: 8}
  t.true(mask.backspace(), 'Valid backspace accepted')
  t.equal(mask.getValue(), '1234 1__4 1234 1234', 'Other selected characters are blanked out')
  t.deepEqual(mask.selection, {start: 6, end: 6}, 'Cursor placed before first character in selection')
})

test('Pasting', function(t) {
  t.plan(10)

  var mask = new InputMask({
    pattern: '1111 1111 1111 1111'
  })

  // Invalid characters at any position will cause a paste to be rejected
  t.false(mask.paste('1234123A12341234'), 'Invalid input rejected')

  // A paste larger than the available remaining space will not be rejected if
  // input was valid up to the end of the editable portion of the mask.
  t.true(mask.paste('12341234123412349'), 'Too large input not rejected')
  t.equal(mask.getValue(), '1234 1234 1234 1234')

  // Pasted input doesn't have to contain static formatting characters...
  mask.setValue('')
  mask.selection = {start: 0, end: 0}
  t.true(mask.paste('1234123412341234'), 'Complete, valid input accepted')
  t.equal(mask.getValue(), '1234 1234 1234 1234', 'Formatted pasted value')
  t.deepEqual(mask.selection, {start: 19, end: 19}, 'Cursor position after paste')

  mask.selection = {start: 0, end: 19}
  t.true(mask.backspace(), 'Backspace to delete content')
  t.equal(mask.getValue(), '____ ____ ____ ____', 'Empty after backspace')

  // Pasted input can contain static formatting characters
  t.true(mask.paste('1234 1234 1234 1234'), 'Pasted value can contain static parts')
  t.equal(mask.getValue(), '1234 1234 1234 1234', 'Value after paste')
})

test('Pasting with leading static pattern in selection', function(t) {
  t.plan(7)

  var mask = new InputMask({
    pattern: '----- 1111 1111'
  })

  // Pasting with cursor in leading static part
  mask.selection = {start: 0, end: 0}
  t.false(mask.paste('1234 5678'), 'Paste fails if leading static pattern not matched')
  t.ok(mask.paste('----- 1234 5678'), 'Paste succeeds when leading static pattern is matched')
  t.equal(mask.getValue(), '----- 1234 5678')

  // Pasting with selection including leading static part
  mask.setValue('')
  t.equal(mask.getValue(), '----- ____ ____')
  mask.selection = {start: 0, end: 15}
  t.false(mask.paste('1'), 'Paste fails if leading static pattern not matched')
  t.ok(mask.paste('----- 1'), 'Paste succeeds when leading static pattern is matched')
  t.equal(mask.getValue(), '----- 1___ ____')
})

test('Setting selection', function(t) {
  t.plan(8)

  var mask = new InputMask({pattern: '----- [[11 1111 ]]-'})
  t.equal(mask.pattern.firstEditableIndex, 8, 'First editable index calculation')
  t.equal(mask.pattern.lastEditableIndex, 14, 'Last editable index calculation')
  // The cursor cannot be placed before the first editable index...
  t.true(mask.setSelection({start: 0, end: 0}), 'Cursor before editable region is changed')
  t.deepEqual(mask.selection, {start: 8, end: 8}, 'Cursor placed at first editable character')
  // ...or beyond the last editable character
  t.true(mask.setSelection({start: 18, end: 18}), 'Cursor after editable region is changed')
  t.deepEqual(mask.selection, {start: 15, end: 15}, 'Cursor placed after last editable character')
  // ...however a selection can span beyond the editable region
  t.false(mask.setSelection({start: 0, end: 19}), 'Selection beyond editable region not changed')
  t.deepEqual(mask.selection, {start: 0, end: 19}, 'Whole value can be selected')
})<|endoftext|>


<commit_before>/** This is the base functionality for a component. */
apogeeapp.app.Component = function(workspaceUI,member,componentGenerator) {

    this.workspaceUI = workspaceUI;
    this.member = member;
    this.uiActiveParent = null;
    this.componentGenerator = componentGenerator;

    this.workspaceUI.registerMember(this.member,this);

    //inheriting objects can pass functions here to be called on cleanup, save, etc
    this.openActions = [];
    this.saveActions = [];
    this.cleanupActions = [];

    //notifications
    this.bannerState = apogeeapp.app.WindowHeaderManager.BANNER_TYPE_NONE;
    this.bannerMessage = "";

    //ui elements
    this.windowDisplay = null;
    this.windowDisplayStateJson = null;

    this.tabDisplay = null;

    this.treeDisplay = null;
}

//These parameters are used to order the components in the tree entry.
apogeeapp.app.Component.DEFAULT_COMPONENT_TYPE_SORT_ORDER = 5;
apogeeapp.app.Component.FOLDER_COMPONENT_TYPE_SORT_ORDER = 0;

/** If an extending object has any open actions to read the open json, a callback should be passed here.
 * The callback will be executed in the context of the current object. */
apogeeapp.app.Component.prototype.addOpenAction = function(openFunction) {
    this.openActions.push(openFunction);
}

/** If an extending object has any save actions, a callback should be passed here.
 * The callback will be executed in the context of the current object. */
apogeeapp.app.Component.prototype.addSaveAction = function(saveFunction) {
    this.saveActions.push(saveFunction);
}

/** If an extending object has any cleanup actions, a callback should be passed here.
 * The callback will be executed in the context of the current object. */
apogeeapp.app.Component.prototype.addCleanupAction = function(cleanupFunction) {
    this.cleanupActions.push(cleanupFunction);
}

apogeeapp.app.Component.DEFAULT_ICON_RES_PATH = "/genericIcon.png";

apogeeapp.app.Component.MENU_ITEM_OPEN = 0x01;

//==============================
// Public Instance Methods
//==============================

/** This method returns the base member for this component. */
apogeeapp.app.Component.prototype.getMember = function() {
    return this.member;
}

/** This method returns the icon url for the component. */
apogeeapp.app.Component.prototype.getIconUrl = function() {
    if(this.componentGenerator.ICON_URL) {
        return this.componentGenerator.ICON_URL;
    }
    else {
        var resPath = this.componentGenerator.ICON_RES_PATH;
        if(!resPath) resPath = apogeeapp.app.Component.DEFAULT_ICON_RES_PATH;
        return apogeeapp.ui.getResourcePath(resPath);
    }
}

/** This method returns the workspace for this component. */
apogeeapp.app.Component.prototype.getWorkspace = function() {
    return this.member.getWorkspace();
}

/** This method returns the workspaceUI for this component. */
apogeeapp.app.Component.prototype.getWorkspaceUI = function() {
    return this.workspaceUI;
}

apogeeapp.app.Component.prototype.getTreeEntry = function(createIfMissing) {
    if((createIfMissing)&&(!this.treeDisplay)) {
        this.treeDisplay = this.instantiateTreeEntry();
        this.treeDisplay.setBannerState(this.bannerState,this.bannerMessage);
    }

    if(this.treeDisplay) {
        return this.treeDisplay.getTreeEntry();
    }
    else {
        return null;
    }
}

/** @protected */
apogeeapp.app.Component.prototype.instantiateTreeEntry = function() {
    var treeDisplay = new apogeeapp.app.TreeComponentDisplay(this);

    //default sort order within parent
    var treeEntrySortOrder = (this.componentGenerator.TREE_ENTRY_SORT_ORDER !== undefined) ? this.componentGenerator.TREE_ENTRY_SORT_ORDER : apogeeapp.app.Component.DEFAULT_COMPONENT_TYPE_SORT_ORDER;
    treeDisplay.setComponentTypeSortOrder(treeEntrySortOrder);

//    if(this.treeDisplay) {
//        this.treeDisplay.setComponentTypeSortOrder(apogeeapp.app.Component.FOLDER_COMPONENT_TYPE_SORT_ORDER);
//    }
//
    return treeDisplay;
}

//implement
///** This creates an instance of the window display.
///* The windowDisplayOverrideOptions are optional options to override ths existing window options from the parent. */
//apogeeapp.app.Component.prototype.instantiateWindowDisplay = function(windowDisplayOverrideOptions);

apogeeapp.app.Component.prototype.getWindowDisplay = function(createIfMissing,windowDisplayOverrideOptions) {
    if((createIfMissing)&&(!this.windowDisplay)) {
        this.windowDisplay = this.instantiateWindowDisplay(windowDisplayOverrideOptions);
        this.windowDisplay.setBannerState(this.bannerState,this.bannerMessage);
    }
    return this.windowDisplay;
}

apogeeapp.app.Component.prototype.closeWindowDisplay = function() {
    if(this.windowDisplay) {
        //first store the window state
        this.windowDisplayStateJson = this.windowDisplay.getStateJson();

        //delete the window
        this.windowDisplay.deleteDisplay();
    }
}

apogeeapp.app.Component.prototype.getMenuItems = function(optionalMenuItemList) {
    //menu items
    var menuItemList = optionalMenuItemList ? optionalMenuItemList : [];

    //add the standard entries
    var itemInfo = {};
    itemInfo.title = "Edit Properties";
    itemInfo.callback = apogeeapp.app.updatecomponent.getUpdateComponentCallback(this);
    menuItemList.push(itemInfo);

    var itemInfo = {};
    itemInfo.title = "Delete";
    itemInfo.callback = this.createDeleteCallback(itemInfo.title);
    menuItemList.push(itemInfo);

    return menuItemList;
}

apogeeapp.app.Component.prototype.getOpenMenuItem = function() {
    var openCallback = this.createOpenCallback();
    if(openCallback) {
        var itemInfo = {};
        itemInfo.title = "Open";
        itemInfo.callback = openCallback;
        return itemInfo;
    }
    else {
        return null;
    }
}

//Implement in extending class:
///** This indicates if the component has a tab display. */
//apogeeapp.app.Component.prototype.usesTabDisplay = function();

//Implement in extending class:
///** This creates the tab display for the component. */
//apogeeapp.app.Component.prototype.instantiateTabDisplay = function();

apogeeapp.app.Component.prototype.getTabDisplay = function(createIfMissing) {
    if((createIfMissing)&&(this.usesTabDisplay())&&(!this.tabDisplay)) {
        this.tabDisplay = this.instantiateTabDisplay();
        this.tabDisplay.setBannerState(this.bannerState,this.bannerMessage);
    }
    return this.tabDisplay;
}

/** This closes the tab display for the component. */
apogeeapp.app.Component.prototype.closeTabDisplay = function() {
    if(this.tabDisplay) {
        this.tabDisplay.closeTab();
        this.tabDisplay = null;
    }
}

/** This deserializes the component. */
apogeeapp.app.Component.prototype.toJson = function() {
    var json = {};
    json.type = this.componentGenerator.uniqueName;

    if(this.windowDisplay != null) {
        this.windowDisplayStateJson = this.windowDisplay.getStateJson();
    }

    if(this.windowDisplayStateJson) {
        json.windowState = this.windowDisplayStateJson;
    }

    if(this.tabDisplay) {
        json.tabOpen = true;
    }

    if(this.treeDisplay) {
        var treeState = this.treeDisplay.getState();
        if(treeState != apogeeapp.ui.treecontrol.NO_CONTROL) {
            json.treeState = treeState;
        }
    }

    for(var i = 0; i < this.saveActions.length; i++) {
        this.saveActions[i].call(this,json);
    }

    return json;
}

/** This serializes the component. */
apogeeapp.app.Component.prototype.loadSerializedValues = function(json) {
    if(!json) json = {};
    this.options = json;

    //take any immediate needed actions

    //set the tree state
    if((this.treeDisplay)&&(json.treeState !== undefined)) {
        this.treeDisplay.setState(json.treeState);
    }

    //open the tab - if tab frame exists
    if((json.tabOpen)&&(this.usesTabDisplay())) {
        var tabFrame = this.workspaceUI.getTabFrame();
        if(tabFrame) {
            //create the tab display if it is not present
            this.getTabDisplay(true);

            var tab = this.tabDisplay.getTab();

            tabFrame.addTab(tab,false);
        }
    }

    //set window options
    if(json.windowState !== undefined) {
        this.windowDisplayStateJson = json.windowState;
    }

    if(json) {
        for(var i = 0; i < this.openActions.length; i++) {
            this.openActions[i].call(this,json);
        }
    }
}
//==============================
// Protected Instance Methods
//==============================

//This method should be populated by an extending object.
//** This serializes the table component. */
//apogeeapp.app.Component.prototype.writeToJson = function(json);

/** This method cleans up after a delete. Any extending object that has delete
 * actions should pass a callback function to the method "addClenaupAction" */
apogeeapp.app.Component.prototype.onDelete = function() {

    //remove from parent
    if(this.uiActiveParent) {
        var parentComponent = this.workspaceUI.getComponent(this.uiActiveParent);
        if(parentComponent) {
            //remove the tree from the parent
            parentComponent.removeChildComponent(this);
        }
    }

    if(this.tabDisplay) {
        this.closeTabDisplay();
    }

    //execute cleanup actions
    for(var i = 0; i < this.cleanupActions.length; i++) {
        this.cleanupActions[i].call(this);
    }
}

/** This method extends the member udpated function from the base.
 * @protected */
apogeeapp.app.Component.prototype.memberUpdated = function() {
    //check for change of parent
    if(this.member.getParent() !== this.uiActiveParent) {
        var oldParent = this.uiActiveParent;
        var newParent = this.member.getParent();

        this.uiActiveParent = newParent;

        //remove from old parent component
        if(oldParent) {
            var oldParentComponent = this.workspaceUI.getComponent(oldParent);
            oldParentComponent.removeChildComponent(this);
            //delete all the window display
            if(this.windowDisplay) {
                this.windowDisplay.deleteDisplay();
                this.windowDisplay = null;
            }
        }

        //add to the new parent component
        if(newParent) {
            var newParentComponent = this.workspaceUI.getComponent(newParent);
            newParentComponent.addChildComponent(this);
        }
    }

    //get the banner info
    var member = this.getMember();
    if(member.hasError()) {
        var errorMsg = "";
        var actionErrors = member.getErrors();
        for(var i = 0; i < actionErrors.length; i++) {
            errorMsg += actionErrors[i].msg + "\n";
        }

        this.bannerState = apogeeapp.app.WindowHeaderManager.BANNER_TYPE_ERROR;
        this.bannerMessage = errorMsg;
    }
    else if(member.getResultPending()) {
        this.bannerState = apogeeapp.app.WindowHeaderManager.BANNER_TYPE_PENDING;
        this.bannerMessage = apogeeapp.app.WindowHeaderManager.PENDING_MESSAGE;

    }
    else {
        this.bannerState = apogeeapp.app.WindowHeaderManager.BANNER_TYPE_NONE;
        this.bannerMessage = null;
    }

    //update for new data
    if(this.treeDisplay) {
        this.treeDisplay.updateData();
        this.treeDisplay.setBannerState(this.bannerState,this.bannerMessage);
    }
    if(this.windowDisplay != null) {
        this.windowDisplay.updateData();
        this.windowDisplay.setBannerState(this.bannerState,this.bannerMessage);
    }
    if(this.tabDisplay != null) {
        this.tabDisplay.updateData();
        this.tabDisplay.setBannerState(this.bannerState,this.bannerMessage);
    }
}

/** This method is used for setting initial values in the property dialog.
 * If there are additional property lines, in the generator, this method should
 * be extended to give the values of those properties too. */
apogeeapp.app.Component.prototype.getPropertyValues = function() {

    var member = this.member;

    var values = {};
    values.name = member.getName();
    var parent = member.getParent();
    if(parent) {
        values.parentName = parent.getFullName();
    }

    if(member.generator.addPropFunction) {
        member.generator.addPropFunction(member,values);
    }
    if(this.componentGenerator.addPropFunction) {
        this.componentGenerator.addPropFunction(this,values);
    }
    return values;
}

//=============================
// Action UI Entry Points
//=============================

/** This method creates a callback for deleting the component.
 *  @private */
apogeeapp.app.Component.prototype.createOpenCallback = function() {
    var instance = this;
    var openCallback;
    var workspaceUI = this.workspaceUI;

    var makeTabActive = function(tabComponent) {
        var tabDisplay = tabComponent.getTabDisplay();
        if(tabDisplay) {
            var tab = tabDisplay.getTab();
            tab.makeActive();
        }
        else {
            var tabDisplay = tabComponent.getTabDisplay(true);
            var tab = tabDisplay.getTab();
            var tabFrame = workspaceUI.getTabFrame();
            tabFrame.addTab(tab,true);
        }
    }

    if(this.usesTabDisplay()) {
        openCallback = function() {
            makeTabActive(instance);
        }
    }
    else {
        //remove the tree from the parent
        openCallback = function() {
            var parent = instance.member.getParent();
            if(parent) {
                var parentComponent = workspaceUI.getComponent(parent);
                if((parentComponent)&&(parentComponent.usesTabDisplay())) {

                    //open the parent and bring this child to the front
                    makeTabActive(parentComponent);
                    parentComponent.showChildComponent(instance);
                }
            }
        }
    }

    return openCallback;
}

/** This method creates a callback for deleting the component.
 *  @private */
apogeeapp.app.Component.prototype.createDeleteCallback = function() {
    var member = this.getMember();
    return function() {
        var doDelete = confirm("Are you sure you want to delete this object?");
        if(!doDelete) {
            return;
        }

        //delete the object - the component we be deleted after the delete event received
        var json = {};
        json.action = "deleteMember";
        json.member = member;
        var actionResponse = apogee.action.doAction(json,true);

        if(!actionResponse.getSuccess()) {
            //show an error message
            apogeeapp.app.errorHandling.handleActionError(actionResponse);
        }
    }
}

//======================================
// Static methods
//======================================

//this creates a component from a member and component options (not property values!)
apogeeapp.app.Component.createComponentFromMember = function(componentGenerator,workspaceUI,member,userInputValues,serializedValues) {

    //create empty component
    var component = new componentGenerator(workspaceUI,member);

    //apply any serialized values
    if(serializedValues) {
        component.loadSerializedValues(serializedValues);
    }

    //apply any user input (property dialog) values
    if((userInputValues)&&(componentGenerator.updateProperties)) {
        componentGenerator.updateProperties(component,null,userInputValues);
    }

    //call member updated to process and notify of component creation
    component.memberUpdated();

    return component;
}

//======================================
// All components should have a generator to creste the component
// from a json. See existing components for examples.
//======================================
<commit_msg>fixed a bug in restoring the tree state when opening a workspace<commit_after>/** This is the base functionality for a component. */
apogeeapp.app.Component = function(workspaceUI,member,componentGenerator) {

    this.workspaceUI = workspaceUI;
    this.member = member;
    this.uiActiveParent = null;
    this.componentGenerator = componentGenerator;

    this.workspaceUI.registerMember(this.member,this);

    //inheriting objects can pass functions here to be called on cleanup, save, etc
    this.openActions = [];
    this.saveActions = [];
    this.cleanupActions = [];

    //notifications
    this.bannerState = apogeeapp.app.WindowHeaderManager.BANNER_TYPE_NONE;
    this.bannerMessage = "";

    //ui elements
    this.windowDisplay = null;
    this.windowDisplayStateJson = null;

    this.tabDisplay = null;

    this.treeDisplay = null;
}

//These parameters are used to order the components in the tree entry.
apogeeapp.app.Component.DEFAULT_COMPONENT_TYPE_SORT_ORDER = 5;
apogeeapp.app.Component.FOLDER_COMPONENT_TYPE_SORT_ORDER = 0;

/** If an extending object has any open actions to read the open json, a callback should be passed here.
 * The callback will be executed in the context of the current object. */
apogeeapp.app.Component.prototype.addOpenAction = function(openFunction) {
    this.openActions.push(openFunction);
}

/** If an extending object has any save actions, a callback should be passed here.
 * The callback will be executed in the context of the current object. */
apogeeapp.app.Component.prototype.addSaveAction = function(saveFunction) {
    this.saveActions.push(saveFunction);
}

/** If an extending object has any cleanup actions, a callback should be passed here.
 * The callback will be executed in the context of the current object. */
apogeeapp.app.Component.prototype.addCleanupAction = function(cleanupFunction) {
    this.cleanupActions.push(cleanupFunction);
}

apogeeapp.app.Component.DEFAULT_ICON_RES_PATH = "/genericIcon.png";

apogeeapp.app.Component.MENU_ITEM_OPEN = 0x01;

//==============================
// Public Instance Methods
//==============================

/** This method returns the base member for this component. */
apogeeapp.app.Component.prototype.getMember = function() {
    return this.member;
}

/** This method returns the icon url for the component. */
apogeeapp.app.Component.prototype.getIconUrl = function() {
    if(this.componentGenerator.ICON_URL) {
        return this.componentGenerator.ICON_URL;
    }
    else {
        var resPath = this.componentGenerator.ICON_RES_PATH;
        if(!resPath) resPath = apogeeapp.app.Component.DEFAULT_ICON_RES_PATH;
        return apogeeapp.ui.getResourcePath(resPath);
    }
}

/** This method returns the workspace for this component. */
apogeeapp.app.Component.prototype.getWorkspace = function() {
    return this.member.getWorkspace();
}

/** This method returns the workspaceUI for this component. */
apogeeapp.app.Component.prototype.getWorkspaceUI = function() {
    return this.workspaceUI;
}

apogeeapp.app.Component.prototype.getTreeEntry = function(createIfMissing) {
    if((createIfMissing)&&(!this.treeDisplay)) {
        this.treeDisplay = this.instantiateTreeEntry();
        this.treeDisplay.setBannerState(this.bannerState,this.bannerMessage);
        if((this.options)&&(this.options.treeState !== undefined)) {
            this.treeDisplay.setState(this.options.treeState);
        }
    }

    if(this.treeDisplay) {
        return this.treeDisplay.getTreeEntry();
    }
    else {
        return null;
    }
}

/** @protected */
apogeeapp.app.Component.prototype.instantiateTreeEntry = function() {
    var treeDisplay = new apogeeapp.app.TreeComponentDisplay(this);

    //default sort order within parent
    var treeEntrySortOrder = (this.componentGenerator.TREE_ENTRY_SORT_ORDER !== undefined) ? this.componentGenerator.TREE_ENTRY_SORT_ORDER : apogeeapp.app.Component.DEFAULT_COMPONENT_TYPE_SORT_ORDER;
    treeDisplay.setComponentTypeSortOrder(treeEntrySortOrder);

//    if(this.treeDisplay) {
//        this.treeDisplay.setComponentTypeSortOrder(apogeeapp.app.Component.FOLDER_COMPONENT_TYPE_SORT_ORDER);
//    }
//
    return treeDisplay;
}

//implement
///** This creates an instance of the window display.
///* The windowDisplayOverrideOptions are optional options to override ths existing window options from the parent. */
//apogeeapp.app.Component.prototype.instantiateWindowDisplay = function(windowDisplayOverrideOptions);

apogeeapp.app.Component.prototype.getWindowDisplay = function(createIfMissing,windowDisplayOverrideOptions) {
    if((createIfMissing)&&(!this.windowDisplay)) {
        this.windowDisplay = this.instantiateWindowDisplay(windowDisplayOverrideOptions);
        this.windowDisplay.setBannerState(this.bannerState,this.bannerMessage);
    }
    return this.windowDisplay;
}

apogeeapp.app.Component.prototype.closeWindowDisplay = function() {
    if(this.windowDisplay) {
        //first store the window state
        this.windowDisplayStateJson = this.windowDisplay.getStateJson();

        //delete the window
        this.windowDisplay.deleteDisplay();
    }
}

apogeeapp.app.Component.prototype.getMenuItems = function(optionalMenuItemList) {
    //menu items
    var menuItemList = optionalMenuItemList ? optionalMenuItemList : [];

    //add the standard entries
    var itemInfo = {};
    itemInfo.title = "Edit Properties";
    itemInfo.callback = apogeeapp.app.updatecomponent.getUpdateComponentCallback(this);
    menuItemList.push(itemInfo);

    var itemInfo = {};
    itemInfo.title = "Delete";
    itemInfo.callback = this.createDeleteCallback(itemInfo.title);
    menuItemList.push(itemInfo);

    return menuItemList;
}

apogeeapp.app.Component.prototype.getOpenMenuItem = function() {
    var openCallback = this.createOpenCallback();
    if(openCallback) {
        var itemInfo = {};
        itemInfo.title = "Open";
        itemInfo.callback = openCallback;
        return itemInfo;
    }
    else {
        return null;
    }
}

//Implement in extending class:
///** This indicates if the component has a tab display. */
//apogeeapp.app.Component.prototype.usesTabDisplay = function();

//Implement in extending class:
///** This creates the tab display for the component. */
//apogeeapp.app.Component.prototype.instantiateTabDisplay = function();

apogeeapp.app.Component.prototype.getTabDisplay = function(createIfMissing) {
    if((createIfMissing)&&(this.usesTabDisplay())&&(!this.tabDisplay)) {
        this.tabDisplay = this.instantiateTabDisplay();
        this.tabDisplay.setBannerState(this.bannerState,this.bannerMessage);
    }
    return this.tabDisplay;
}

/** This closes the tab display for the component. */
apogeeapp.app.Component.prototype.closeTabDisplay = function() {
    if(this.tabDisplay) {
        this.tabDisplay.closeTab();
        this.tabDisplay = null;
    }
}

/** This deserializes the component. */
apogeeapp.app.Component.prototype.toJson = function() {
    var json = {};
    json.type = this.componentGenerator.uniqueName;

    if(this.windowDisplay != null) {
        this.windowDisplayStateJson = this.windowDisplay.getStateJson();
    }

    if(this.windowDisplayStateJson) {
        json.windowState = this.windowDisplayStateJson;
    }

    if(this.tabDisplay) {
        json.tabOpen = true;
    }

    if(this.treeDisplay) {
        var treeState = this.treeDisplay.getState();
        if(treeState != apogeeapp.ui.treecontrol.NO_CONTROL) {
            json.treeState = treeState;
        }
    }

    for(var i = 0; i < this.saveActions.length; i++) {
        this.saveActions[i].call(this,json);
    }

    return json;
}

/** This serializes the component. */
apogeeapp.app.Component.prototype.loadSerializedValues = function(json) {
    if(!json) json = {};
    this.options = json;

    //take any immediate needed actions

    //set the tree state
    if((this.treeDisplay)&&(json.treeState !== undefined)) {
        this.treeDisplay.setState(json.treeState);
    }

    //open the tab - if tab frame exists
    if((json.tabOpen)&&(this.usesTabDisplay())) {
        var tabFrame = this.workspaceUI.getTabFrame();
        if(tabFrame) {
            //create the tab display if it is not present
            this.getTabDisplay(true);

            var tab = this.tabDisplay.getTab();

            tabFrame.addTab(tab,false);
        }
    }

    //set window options
    if(json.windowState !== undefined) {
        this.windowDisplayStateJson = json.windowState;
    }

    if(json) {
        for(var i = 0; i < this.openActions.length; i++) {
            this.openActions[i].call(this,json);
        }
    }
}
//==============================
// Protected Instance Methods
//==============================

//This method should be populated by an extending object.
//** This serializes the table component. */
//apogeeapp.app.Component.prototype.writeToJson = function(json);

/** This method cleans up after a delete. Any extending object that has delete
 * actions should pass a callback function to the method "addClenaupAction" */
apogeeapp.app.Component.prototype.onDelete = function() {

    //remove from parent
    if(this.uiActiveParent) {
        var parentComponent = this.workspaceUI.getComponent(this.uiActiveParent);
        if(parentComponent) {
            //remove the tree from the parent
            parentComponent.removeChildComponent(this);
        }
    }

    if(this.tabDisplay) {
        this.closeTabDisplay();
    }

    //execute cleanup actions
    for(var i = 0; i < this.cleanupActions.length; i++) {
        this.cleanupActions[i].call(this);
    }
}

/** This method extends the member udpated function from the base.
 * @protected */
apogeeapp.app.Component.prototype.memberUpdated = function() {
    //check for change of parent
    if(this.member.getParent() !== this.uiActiveParent) {
        var oldParent = this.uiActiveParent;
        var newParent = this.member.getParent();

        this.uiActiveParent = newParent;

        //remove from old parent component
        if(oldParent) {
            var oldParentComponent = this.workspaceUI.getComponent(oldParent);
            oldParentComponent.removeChildComponent(this);
            //delete all the window display
            if(this.windowDisplay) {
                this.windowDisplay.deleteDisplay();
                this.windowDisplay = null;
            }
        }

        //add to the new parent component
        if(newParent) {
            var newParentComponent = this.workspaceUI.getComponent(newParent);
            newParentComponent.addChildComponent(this);
        }
    }

    //get the banner info
    var member = this.getMember();
    if(member.hasError()) {
        var errorMsg = "";
        var actionErrors = member.getErrors();
        for(var i = 0; i < actionErrors.length; i++) {
            errorMsg += actionErrors[i].msg + "\n";
        }

        this.bannerState = apogeeapp.app.WindowHeaderManager.BANNER_TYPE_ERROR;
        this.bannerMessage = errorMsg;
    }
    else if(member.getResultPending()) {
        this.bannerState = apogeeapp.app.WindowHeaderManager.BANNER_TYPE_PENDING;
        this.bannerMessage = apogeeapp.app.WindowHeaderManager.PENDING_MESSAGE;

    }
    else {
        this.bannerState = apogeeapp.app.WindowHeaderManager.BANNER_TYPE_NONE;
        this.bannerMessage = null;
    }

    //update for new data
    if(this.treeDisplay) {
        this.treeDisplay.updateData();
        this.treeDisplay.setBannerState(this.bannerState,this.bannerMessage);
    }
    if(this.windowDisplay != null) {
        this.windowDisplay.updateData();
        this.windowDisplay.setBannerState(this.bannerState,this.bannerMessage);
    }
    if(this.tabDisplay != null) {
        this.tabDisplay.updateData();
        this.tabDisplay.setBannerState(this.bannerState,this.bannerMessage);
    }
}

/** This method is used for setting initial values in the property dialog.
 * If there are additional property lines, in the generator, this method should
 * be extended to give the values of those properties too. */
apogeeapp.app.Component.prototype.getPropertyValues = function() {

    var member = this.member;

    var values = {};
    values.name = member.getName();
    var parent = member.getParent();
    if(parent) {
        values.parentName = parent.getFullName();
    }

    if(member.generator.addPropFunction) {
        member.generator.addPropFunction(member,values);
    }
    if(this.componentGenerator.addPropFunction) {
        this.componentGenerator.addPropFunction(this,values);
    }
    return values;
}

//=============================
// Action UI Entry Points
//=============================

/** This method creates a callback for deleting the component.
 *  @private */
apogeeapp.app.Component.prototype.createOpenCallback = function() {
    var instance = this;
    var openCallback;
    var workspaceUI = this.workspaceUI;

    var makeTabActive = function(tabComponent) {
        var tabDisplay = tabComponent.getTabDisplay();
        if(tabDisplay) {
            var tab = tabDisplay.getTab();
            tab.makeActive();
        }
        else {
            var tabDisplay = tabComponent.getTabDisplay(true);
            var tab = tabDisplay.getTab();
            var tabFrame = workspaceUI.getTabFrame();
            tabFrame.addTab(tab,true);
        }
    }

    if(this.usesTabDisplay()) {
        openCallback = function() {
            makeTabActive(instance);
        }
    }
    else {
        //remove the tree from the parent
        openCallback = function() {
            var parent = instance.member.getParent();
            if(parent) {
                var parentComponent = workspaceUI.getComponent(parent);
                if((parentComponent)&&(parentComponent.usesTabDisplay())) {

                    //open the parent and bring this child to the front
                    makeTabActive(parentComponent);
                    parentComponent.showChildComponent(instance);
                }
            }
        }
    }

    return openCallback;
}

/** This method creates a callback for deleting the component.
 *  @private */
apogeeapp.app.Component.prototype.createDeleteCallback = function() {
    var member = this.getMember();
    return function() {
        var doDelete = confirm("Are you sure you want to delete this object?");
        if(!doDelete) {
            return;
        }

        //delete the object - the component we be deleted after the delete event received
        var json = {};
        json.action = "deleteMember";
        json.member = member;
        var actionResponse = apogee.action.doAction(json,true);

        if(!actionResponse.getSuccess()) {
            //show an error message
            apogeeapp.app.errorHandling.handleActionError(actionResponse);
        }
    }
}

//======================================
// Static methods
//======================================

//this creates a component from a member and component options (not property values!)
apogeeapp.app.Component.createComponentFromMember = function(componentGenerator,workspaceUI,member,userInputValues,serializedValues) {

    //create empty component
    var component = new componentGenerator(workspaceUI,member);

    //apply any serialized values
    if(serializedValues) {
        component.loadSerializedValues(serializedValues);
    }

    //apply any user input (property dialog) values
    if((userInputValues)&&(componentGenerator.updateProperties)) {
        componentGenerator.updateProperties(component,null,userInputValues);
    }

    //call member updated to process and notify of component creation
    component.memberUpdated();

    return component;
}

//======================================
// All components should have a generator to creste the component
// from a json. See existing components for examples.
//======================================
<|endoftext|>


<commit_before>import * as Babel from "babel-core";
import Chai from "chai";

Chai.config.showDiff = true;

const expect = Chai.expect;

const config = {
	compact: true,
	plugins: [
		"transform-async-to-generator",
		"transform-class-properties",
		"transform-decorators-legacy",
		"transform-es2015-destructuring",
		"transform-es2015-modules-commonjs",
		"transform-exponentiation-operator",
		"transform-function-bind",
		"transform-object-rest-spread",
		"transform-react-jsx"
	]
};

const normalize = (code) => {
	return code.replace(/\s|"use strict";/g, "");
};

const transform = (code) => {
	return Babel.transform(code, config).code;
};

const test = (input, isSupported, isNative, custom) => {
	if (input) {
		if (isSupported) {
			const transformed = transform(input);
			if (isNative) {
				expect(normalize(transformed)).to.equal(normalize(input));
			} else {
				expect(normalize(transformed)).not.to.equal(normalize(input));
			}
			expect(() => {
				eval(transformed); // eslint-disable-line no-eval
			}).not.to.throw();
		} else {
			expect(() => {
				eval(transform(input)); // eslint-disable-line no-eval
			}).to.throw();
		}
	}
	if (typeof custom === "function") {
		custom();
	}
};

describe("Syntax", () => {
	describe("Node", () => {
		[
			["array inclusion check", "[\"foo\"].includes(\"foo\");"],
			["arrow functions", "const foo = () => {};"],
			["classes", `
				let Foo = class Foo {
					bar() {}
				};
				let Bar = class Bar extends Foo {
					foo() {
						super.bar();
					}
				};
			`],
			["computed properties", "const foo = { [\"bar\"]: \"foo\" };"],
			["default parameters", "const foo = (bar = \"foo\") => {};"],
			["destructuring parameters", "const foo = ({ bar }) => {};"],
			["for-of loops", "for (const foo of [\"bar\"]) {}"],
			["generators", `
				function * foo() {
					yield "bar";
				}
			`],
			["object prototypes", `
				const foo = { bar() {} };
				const bar = {
					foo() {
						super.bar();
					}
				};
				Object.setPrototypeOf(bar, foo);
			`],
			["rest parameters", `
				const foo = (...bar) => {
					foo(bar);
				};
			`],
			["scoped variables", `
				const foo = "bar";
				let bar = "foo";
			`],
			["shorthand object properties", `
				const foo = "bar";
				const bar = { foo };
			`],
			["sticky regular expressions", "\"foo\".match(/foo/y);"],
			["symbols", "const foo = Symbol();"],
			["template strings", `const foo = \`${"foo"}bar\`;`],
			["unicode literals", null, () => {
				expect("\u6b7b").to.equal("死");
			}],
			["unicode regular expressions", "\"死\".match(/死/u);"]
		].forEach((feature) => {
			it(`should support ${feature[0]}`, () => {
				test(feature[1], true, true, feature[2]);
			});
		});
	});
	describe("Babel", () => {
		[
			["JSX", `
				import React from "./react";
				class Foo extends React.Component {}
				<Foo />;
			`],
			["asynchronous functions", `
				const foo = async() => {
					await new Promise((resolve) => {
						resolve();
					})
				}
			`],
			["class properties", `
				let Foo = class Foo {
					foo = "bar";
					static bar = "foo";
				};
			`],
			["decorators", `
				function foo() {
					return () => {};
				}
				@foo()
				class Bar {}
			`],
			["destructuring", `
				const foo = { bar: "foo" };
				const { bar } = foo;
			`],
			["exponentiation operator", "const foo = 1 ** 1;"],
			["function bind operator", `
				const foo = { bar() {} };
				::foo.bar();
			`],
			["modules", "import Empty from \"./empty\";"],
			["rest array destructuring", `
				const foo = ["foo", "bar"];
				const [bar, ...rest] = foo;
			`],
			["rest object destructuring", `
				const foo = { bar: "foo", foo: "bar" };
				const { bar, ...rest } = foo;
			`]
		].forEach((feature) => {
			it(`should support ${feature[0]}`, () => {
				test(feature[1], true, false, feature[2]);
			});
		});
	});
	describe("Nothing", () => {
		[
			["anonymous function names", null, () => {
				const foo = () => {
					// Do nothing
				};
				expect(foo.name).not.to.equal("foo");
			}]
		].forEach((feature) => {
			it(`should support ${feature[0]}`, () => {
				test(feature[1], false, false, feature[2]);
			});
		});
	});
});
<commit_msg>Updated supported features<commit_after>import * as Babel from "babel-core";
import Chai, { expect } from "chai";

Chai.config.showDiff = true;

const config = {
	compact: true,
	plugins: [
		"transform-async-to-generator",
		"transform-class-properties",
		"transform-decorators-legacy",
		"transform-es2015-modules-commonjs",
		"transform-function-bind",
		"transform-object-rest-spread",
		"transform-react-jsx"
	]
};

const normalize = (code) => {
	return code.replace(/\s|"use strict";/g, "");
};

const transform = (code) => {
	return Babel.transform(code, config).code;
};

const test = (input, isSupported, isNative, custom) => {
	if (input) {
		if (isSupported) {
			const transformed = transform(input);
			if (isNative) {
				expect(normalize(transformed)).to.equal(normalize(input));
			} else {
				expect(normalize(transformed)).not.to.equal(normalize(input));
			}
			expect(() => {
				eval(transformed); // eslint-disable-line no-eval
			}).not.to.throw();
		} else {
			expect(() => {
				eval(transform(input)); // eslint-disable-line no-eval
			}).to.throw();
		}
	}
	if (typeof custom === "function") {
		custom();
	}
};

describe("Syntax", () => {
	describe("Node", () => {
		[
			["anonymous function names", null, () => {
				const foo = () => {
					// Do nothing
				};
				expect(foo.name).to.equal("foo");
			}],
			["array inclusion check", "[\"foo\"].includes(\"foo\");"],
			["arrow functions", "const foo = () => {};"],
			["classes", `
				let Foo = class Foo {
					bar() {}
				};
				let Bar = class Bar extends Foo {
					foo() {
						super.bar();
					}
				};
			`],
			["computed properties", "const foo = { [\"bar\"]: \"foo\" };"],
			["default parameters", "const foo = (bar = \"foo\") => {};"],
			["destructuring", `
				const foo = { bar: "foo" };
				const { bar } = foo;
			`],
			["destructuring parameters", "const foo = ({ bar }) => {};"],
			["exponentiation operator", "const foo = 1 ** 1;"],
			["for-of loops", "for (const foo of [\"bar\"]) {}"],
			["generators", `
				function * foo() {
					yield "bar";
				}
			`],
			["object prototypes", `
				const foo = { bar() {} };
				const bar = {
					foo() {
						super.bar();
					}
				};
				Object.setPrototypeOf(bar, foo);
			`],
			["rest array destructuring", `
				const foo = ["foo", "bar"];
				const [bar, ...rest] = foo;
			`],
			["rest parameters", `
				const foo = (...bar) => {
					foo(bar);
				};
			`],
			["scoped variables", `
				const foo = "bar";
				let bar = "foo";
			`],
			["shorthand object properties", `
				const foo = "bar";
				const bar = { foo };
			`],
			["sticky regular expressions", "\"foo\".match(/foo/y);"],
			["symbols", "const foo = Symbol();"],
			["template strings", `const foo = \`${"foo"}bar\`;`],
			["unicode literals", null, () => {
				expect("\u6b7b").to.equal("死");
			}],
			["unicode regular expressions", "\"死\".match(/死/u);"]
		].forEach((feature) => {
			it(`should support ${feature[0]}`, () => {
				test(feature[1], true, true, feature[2]);
			});
		});
	});
	describe("Babel", () => {
		[
			["JSX", `
				import React from "./react";
				class Foo extends React.Component {}
				<Foo />;
			`],
			["asynchronous functions", `
				const foo = async() => {
					await new Promise((resolve) => {
						resolve();
					});
				};
			`],
			["class properties", `
				let Foo = class Foo {
					foo = "bar";
					static bar = "foo";
				};
			`],
			["decorators", `
				function foo() {
					return () => {};
				}
				@foo()
				class Bar {}
			`],
			["function bind operator", `
				const foo = { bar() {} };
				::foo.bar();
			`],
			["modules", "import Empty from \"./empty\";"],
			["rest object destructuring", `
				const foo = { bar: "foo", foo: "bar" };
				const { bar, ...rest } = foo;
			`]
		].forEach((feature) => {
			it(`should support ${feature[0]}`, () => {
				test(feature[1], true, false, feature[2]);
			});
		});
	});
	describe("Nothing", () => {
		[].forEach((feature) => {
			it(`should support ${feature[0]}`, () => {
				test(feature[1], false, false, feature[2]);
			});
		});
	});
});
<|endoftext|>


<commit_before>var chai = require("chai");
chai.use(require("chai-as-promised"));
chai.use(require("chai-spies"));

var expect = chai.expect;

var Theatre = require("../theatre");

describe("Theatre", function () {
  var app = new Theatre();

  beforeEach(function () {
    app = new Theatre();
  });

  describe("#resolve()", function () {

    it("should call the class constructor", function () {
      var spy = chai.spy("constructor");

      function Test() {
        spy();
      }

      return app.resolve(Test)
        .then(function () {
          expect(spy).to.have.been.called();
        });
    });

    it("should return a promise for the class instance", function () {
      function Test() {}

      return app.resolve(Test)
        .then(function (instance) {
          expect(instance).to.be.instanceof(Test);
        });
    });

    it("should return a promise for an array of instances", function () {
      function Test1() {}
      function Test2() {}

      return app.resolve([Test1, Test2])
        .spread(function (test1, test2) {
          expect(test1).to.be.instanceof(Test1);
          expect(test2).to.be.instanceof(Test2);
        });
    });

    it("should resolve and inject dependencies", function () {
      var spy = chai.spy("constructor");

      function Test1(test2) {
        expect(test2).to.be.instanceof(Test2);
        spy();
      }
      function Test2() { }

      Test1.__theatre = {
        inject: [Test2]
      };

      return app.resolve(Test1)
        .then(function () {
          expect(spy).to.have.been.called();
        });
    });

    it("should detect dependency cycles of length 1", function () {
      function Test1() {}

      Test1.__theatre = { inject: [Test1] };

      return expect(app.resolve(Test1))
        .to.be.rejectedWith(Error, "Cycle detected in class dependencies: Test1 -> Test1");
    });

    it("should detect dependency cycles of length 2", function () {
      function Test1() {}
      function Test2() {}

      Test1.__theatre = { inject: [Test2] };
      Test2.__theatre = { inject: [Test1] };

      return expect(app.resolve(Test1))
        .to.be.rejectedWith(Error, "Cycle detected in class dependencies: Test1 -> Test2 -> Test1");
    });

    it("should detect dependency cycles of length 3", function () {
      function Test1() {}
      function Test2() {}
      function Test3() {}

      Test1.__theatre = { inject: [Test2] };
      Test2.__theatre = { inject: [Test3] };
      Test3.__theatre = { inject: [Test1] };

      return expect(app.resolve(Test1))
        .to.be.rejectedWith(Error, "Cycle detected in class dependencies: Test1 -> Test2 -> Test3 -> Test1");
    });

  });

  describe("#addOverride()", function () {

    it("should override a class", function () {
      function Test1() {}
      function Test2() {}

      app.addOverride(Test1, Test2);

      return app.resolve(Test1).then(function (test) {
        expect(test).to.be.instanceof(Test2);
      });
    });

  });

  describe("#removeOverride()", function () {

    it("should remove override for a class", function () {
      function Test1() {}
      function Test2() {}

      app.addOverride(Test1, Test2);
      app.removeOverride(Test1);

      return app.resolve(Test1).then(function (test) {
        expect(test).to.be.instanceof(Test1);
      });
    });

  });

  describe("#removeAllOverrides()", function () {

    it("should remove all overrides", function () {
      function Test1() {}
      function Test2() {}
      function Test3() {}

      app.addOverride(Test1, Test3);
      app.addOverride(Test2, Test3);
      app.removeAllOverrides();

      return app.resolve([Test1, Test2])
        .spread(function (test1, test2) {
          expect(test1).to.be.instanceof(Test1);
          expect(test2).to.be.instanceof(Test2);
        });
    });

  });
});<commit_msg>Update tests<commit_after>var chai = require("chai");
chai.use(require("chai-as-promised"));
chai.use(require("chai-spies"));

var expect = chai.expect;
var assert = chai.assert;

var Theatre = require("../theatre");

describe("Theatre", function () {
  var app = new Theatre();

  beforeEach(function () {
    app = new Theatre();
  });

  describe("#resolve()", function () {

    describe("with single class", function () {

      it("should call the class constructor", function () {
        function Test() {}

        Test = chai.spy(Test);

        return app.resolve(Test)
          .then(function () {
            expect(Test).to.have.been.called();
          });
      });

      it("should return a promise for the instance", function () {
        function Test() {}

        return expect(app.resolve(Test))
          .to.eventually.be.instanceof(Test);
      });

      it("should call #resolve() dependencies", function () {
        app.resolve = chai.spy(app.resolve);

        function Test1() {}
        function Test2() {}

        Test1.__theatre = {
          inject: [Test2]
        };

        return app.resolve(Test1)
          .then(function () {
            expect(app.resolve).to.have.been.called.with(Test2);
          });
      });

      it("should inject dependencies", function () {
        function Test1(test2) {
          expect(test2).to.be.instanceof(Test2);
        }
        function Test2() {}

        Test1 = chai.spy(Test1);

        Test1.__theatre = {
          inject: [Test2]
        };

        return app.resolve(Test1)
          .then(function () {
            expect(Test1).to.have.been.called();
          });
      });

      it("should detect dependency cycles of length 1", function () {
        function Test1() {}

        Test1.__theatre = { inject: [Test1] };

        return expect(app.resolve(Test1))
          .to.be.rejectedWith(Error, "Cycle detected in class dependencies: Test1 -> Test1");
      });

      it("should detect dependency cycles of length 2", function () {
        function Test1() {}
        function Test2() {}

        Test1.__theatre = { inject: [Test2] };
        Test2.__theatre = { inject: [Test1] };

        return expect(app.resolve(Test1))
          .to.be.rejectedWith(Error, "Cycle detected in class dependencies: Test1 -> Test2 -> Test1");
      });

      it("should detect dependency cycles of length 3", function () {
        function Test1() {}
        function Test2() {}
        function Test3() {}

        Test1.__theatre = { inject: [Test2] };
        Test2.__theatre = { inject: [Test3] };
        Test3.__theatre = { inject: [Test1] };

        return expect(app.resolve(Test1))
          .to.be.rejectedWith(Error, "Cycle detected in class dependencies: Test1 -> Test2 -> Test3 -> Test1");
      });

    });

    describe("with an array of classes", function () {

      it("should call #resolve() for each class", function () {
        app.resolve = chai.spy(app.resolve.bind(app));

        function Test1() {}
        function Test2() {}

        return app.resolve([Test1, Test2])
          .spread(function (test1, test2) {
            expect(app.resolve).to.have.been.called.with(Test1);
            expect(app.resolve).to.have.been.called.with(Test2);
          });
      });

      it("should return a promise for an array of instances", function () {
        function Test1() {}
        function Test2() {}

        return app.resolve([Test1, Test2])
          .then(function (instances) {
            expect(instances[0]).to.be.instanceof(Test1);
            expect(instances[1]).to.be.instanceof(Test2);
          });
      });

    });

    describe("with a singleton class", function () {

      it("should call the class constructor once", function () {
        function Test() {}
        Test = chai.spy(Test);

        Test.__theatre = {
          single: true
        };

        return app.resolve([Test, Test])
          .then(function () {
            expect(Test).to.have.been.called.once();
          });
      });

      it("should return the same instance", function () {
        function Test() {}

        Test.__theatre = {
          single: true
        };

        return app.resolve([Test, Test])
          .spread(function (test1, test2) {
            expect(test1).to.equal(test2);
          });
      });

    });

    describe("with an overridden class", function () {

      it("should detect dependency cycles", function () {
        function Test1() {}
        function Test2() {}
        function MockTest2() {}

        Test1.__theatre = { inject: [Test2] };
        Test2.__theatre = { inject: [Test1] };
        MockTest2.__theatre = { inject: [Test1] };

        app.addOverride(Test2, MockTest2);

        return expect(app.resolve(Test1))
          .to.be.rejectedWith(Error, "Cycle detected in class dependencies: Test1 -> MockTest2 -> Test1");
      });

    });

  });

  describe("#addOverride()", function () {

    it("should add an override for a class", function () {
      function Test1() {}
      function Test2() {}

      app.addOverride(Test1, Test2);

      expect(app._overrides.has(Test1)).to.equal(true);
      expect(app._overrides.get(Test1)).to.equal(Test2);
    });

  });

  describe("#removeOverride()", function () {

    it("should remove override for a class", function () {
      function Test1() {}
      function Test2() {}

      app.addOverride(Test1, Test2);
      app.removeOverride(Test1);

      expect(app._overrides.has(Test1)).to.equal(false);
    });

  });

  describe("#removeAllOverrides()", function () {

    it("should remove all overrides", function () {
      function Test1() {}
      function Test2() {}
      function Test3() {}

      app.addOverride(Test1, Test3);
      app.addOverride(Test2, Test3);
      app.removeAllOverrides();

      expect(app._overrides.has(Test1)).to.equal(false);
      expect(app._overrides.has(Test2)).to.equal(false);
    });

  });
});<|endoftext|>


<commit_before>'use strict';
/*global setImmediate:true*/
/*eslint no-path-concat:0*/
/*eslint default-case:0*/
/**
 * Testing dependencies
 */

var mqtt = require('..'),
  should = require('should'),
  abstractClientTests = require('./abstract_client'),
  setImmediate = global.setImmediate || function (callback) {
    // works in node v0.8
    process.nextTick(callback);
  },
  net = require('net'),
  eos = require('end-of-stream'),
  port = 9876,
  server;

/**
 * Test server
 */
function buildServer () {
  return new mqtt.Server(function (client) {

    client.on('connect', function (packet) {
      if ('invalid' === packet.clientId) {
        client.connack({returnCode: 2});
      } else {
        client.connack({returnCode: 0});
      }
    });

    client.on('publish', function (packet) {
      setImmediate(function () {
        /*jshint -W027*/
        switch (packet.qos) {
          case 0:
            break;
          case 1:
            client.puback(packet);
            break;
          case 2:
            client.pubrec(packet);
            break;
        }
        /*jshint +W027*/
      });
    });

    client.on('pubrel', function (packet) {
      client.pubcomp(packet);
    });

    client.on('pubrec', function (packet) {
      client.pubrel(packet);
    });

    client.on('pubcomp', function (/*packet*/) {
      // Nothing to be done
    });

    client.on('subscribe', function (packet) {
      client.suback({
        messageId: packet.messageId,
        granted: packet.subscriptions.map(function (e) {
          return e.qos;
        })
      });
    });

    client.on('unsubscribe', function (packet) {
      client.unsuback(packet);
    });

    client.on('pingreq', function (/*packet*/) {
      client.pingresp();
    });
  });
}

server = buildServer().listen(port);

describe('MqttClient', function () {
  describe('creating', function () {
    it('should allow instantiation of MqttClient without the \'new\' operator', function (done) {
      should(function () {
        var client;
        try {
          client = mqtt.MqttClient(function () {
            throw Error('break');
          }, {});
          client.end();
        } catch (err) {
          if ('break' !== err.message) {
            throw err;
          }
          done();
        }
      }).not.throw('Object #<Object> has no method \'_setupStream\'');
    });
  });

  var config = { protocol: 'mqtt', port: port };
  abstractClientTests(server, config);

  describe('message ids', function () {
    it('should increment the message id', function () {
      var client = mqtt.connect(config),
        currentId = client._nextId();

      client._nextId().should.equal(currentId + 1);
    });

    it('should return 1 once the interal counter reached limit', function () {
      var client = mqtt.connect(config);
      client.nextId = 65535;

      client._nextId().should.equal(65535);
      client._nextId().should.equal(1);
    });
  });

  describe('reconnecting', function () {
    it('should attempt to reconnect once server is down', function (done) {
      this.timeout(15000);

      var fork = require('child_process').fork,
        // better use path.resolve(__dirname, 'helpers', "server_process.js")
        // with this you are system path joining aware
        innerServer = fork(__dirname + '/helpers/server_process.js'),
        client = mqtt.connect({ port: 3000, host: 'localhost', keepalive: 1 });

      client.once('connect', function () {
        innerServer.kill('SIGINT'); // mocks server shutdown

        client.once('close', function () {
          should.exist(client.reconnectTimer);
          done();
        });
      });
    });

    it('should reconnect to multiple host-ports combination if servers is passed', function (done) {
      this.timeout(15000);

      var server2 = buildServer().listen(port + 42);

      server2.on('client', function (c) {
        c.stream.destroy();
        server2.close();
      });

      server2.on('listening', function () {

        var client = mqtt.connect({ servers: [
          { port: port + 42, host: 'localhost' },
          { port: port, host: 'localhost' }
        ], keepalive: 50 });

        server.once('client', function (serverClient) {
          serverClient.disconnect();
          done();
        });

        client.once('connect', function () {
          client.stream.destroy();
        });
      });
    });

    it('should reconnect if a connack is not received in an interval', function (done) {
      this.timeout(2000);

      var server2 = net.createServer().listen(port + 43);

      server2.on('connection', function (c) {
        eos(c, function () {
          server2.close();
        });
      });

      server2.on('listening', function () {

        var client = mqtt.connect({ servers: [
          { port: port + 43, host: 'localhost' },
          { port: port, host: 'localhost' }
        ], connectTimeout: 500 });

        server.once('client', function (serverClient) {
          serverClient.disconnect();
          done();
        });

        client.once('connect', function () {
          client.stream.destroy();
        });
      });
    });

    it('shoud not be cleared by the connack timer', function (done) {
      this.timeout(4000);

      var server2 = net.createServer().listen(port + 44);

      server2.on('connection', function (c) {
        c.destroy();
      });

      server2.once('listening', function () {
        var reconnects = 0,
          connectTimeout = 1000,
          reconnectPeriod = 100,
          expectedReconnects = Math.floor(connectTimeout / reconnectPeriod),
          client = mqtt.connect({
            port: port + 44,
            host: 'localhost',
            connectTimeout: connectTimeout,
            reconnectPeriod: reconnectPeriod });

        client.on('reconnect', function () {
          reconnects++;
          if (reconnects >= expectedReconnects) {
            client.end();
            done();
          }
        });
      });
    });
  });
});
<commit_msg>test: fix bad behavior when offline<commit_after>'use strict';
/*global setImmediate:true*/
/*eslint no-path-concat:0*/
/*eslint default-case:0*/
/**
 * Testing dependencies
 */

var mqtt = require('..'),
  should = require('should'),
  abstractClientTests = require('./abstract_client'),
  setImmediate = global.setImmediate || function (callback) {
    // works in node v0.8
    process.nextTick(callback);
  },
  net = require('net'),
  eos = require('end-of-stream'),
  port = 9876,
  server;

/**
 * Test server
 */
function buildServer () {
  return new mqtt.Server(function (client) {

    client.on('connect', function (packet) {
      if ('invalid' === packet.clientId) {
        client.connack({returnCode: 2});
      } else {
        client.connack({returnCode: 0});
      }
    });

    client.on('publish', function (packet) {
      setImmediate(function () {
        /*jshint -W027*/
        switch (packet.qos) {
          case 0:
            break;
          case 1:
            client.puback(packet);
            break;
          case 2:
            client.pubrec(packet);
            break;
        }
        /*jshint +W027*/
      });
    });

    client.on('pubrel', function (packet) {
      client.pubcomp(packet);
    });

    client.on('pubrec', function (packet) {
      client.pubrel(packet);
    });

    client.on('pubcomp', function (/*packet*/) {
      // Nothing to be done
    });

    client.on('subscribe', function (packet) {
      client.suback({
        messageId: packet.messageId,
        granted: packet.subscriptions.map(function (e) {
          return e.qos;
        })
      });
    });

    client.on('unsubscribe', function (packet) {
      client.unsuback(packet);
    });

    client.on('pingreq', function (/*packet*/) {
      client.pingresp();
    });
  });
}

server = buildServer().listen(port);

describe('MqttClient', function () {
  describe('creating', function () {
    it('should allow instantiation of MqttClient without the \'new\' operator', function (done) {
      should(function () {
        var client;
        try {
          client = mqtt.MqttClient(function () {
            throw Error('break');
          }, {});
          client.end();
        } catch (err) {
          if ('break' !== err.message) {
            throw err;
          }
          done();
        }
      }).not.throw('Object #<Object> has no method \'_setupStream\'');
    });
  });

  var config = { protocol: 'mqtt', port: port };
  abstractClientTests(server, config);

  describe('message ids', function () {
    it('should increment the message id', function () {
      var client = mqtt.connect(config),
        currentId = client._nextId();

      client._nextId().should.equal(currentId + 1);
    });

    it('should return 1 once the interal counter reached limit', function () {
      var client = mqtt.connect(config);
      client.nextId = 65535;

      client._nextId().should.equal(65535);
      client._nextId().should.equal(1);
    });
  });

  describe('reconnecting', function () {
    it('should attempt to reconnect once server is down', function (done) {
      this.timeout(15000);

      var fork = require('child_process').fork,
        // better use path.resolve(__dirname, 'helpers', "server_process.js")
        // with this you are system path joining aware
        innerServer = fork(__dirname + '/helpers/server_process.js'),
        client = mqtt.connect({ port: 3000, host: 'localhost', keepalive: 1 });

      client.once('connect', function () {
        innerServer.kill('SIGINT'); // mocks server shutdown

        client.once('close', function () {
          should.exist(client.reconnectTimer);
          done();
        });
      });
    });

    it('should reconnect to multiple host-ports combination if servers is passed', function (done) {
      this.timeout(15000);

      var server2 = buildServer().listen(port + 42);

      server2.on('client', function (c) {
        c.stream.destroy();
        server2.close();
      });

      server2.on('listening', function () {

        var client = mqtt.connect({ servers: [
          { port: port + 42, host: 'localhost' },
          { port: port, host: 'localhost' }
        ], keepalive: 50 });

        server.once('client', function (serverClient) {
          serverClient.disconnect();
          done();
        });

        client.once('connect', function () {
          client.stream.destroy();
        });
      });
    });

    it('should reconnect if a connack is not received in an interval', function (done) {
      this.timeout(2000);

      var server2 = net.createServer().listen(port + 43);

      server2.on('connection', function (c) {
        eos(c, function () {
          server2.close();
        });
      });

      server2.on('listening', function () {

        var client = mqtt.connect({ servers: [
          { port: port + 43, host: 'localhost' },
          { port: port, host: 'localhost' }
        ], connectTimeout: 500 });

        server.once('client', function (serverClient) {
          serverClient.disconnect();
          done();
        });

        client.once('connect', function () {
          client.stream.destroy();
        });
      });
    });

    it('shoud not be cleared by the connack timer', function (done) {
      this.timeout(4000);

      var server2 = net.createServer().listen(port + 44);

      server2.on('connection', function (c) {
        c.destroy();
      });

      server2.once('listening', function () {
        var reconnects = 0,
          connectTimeout = 1000,
          reconnectPeriod = 100,
          expectedReconnects = Math.floor(connectTimeout / reconnectPeriod),
          client = mqtt.connect({
            port: port + 44,
            host: 'localhost',
            connectTimeout: connectTimeout,
            reconnectPeriod: reconnectPeriod });

        client.on('reconnect', function () {
          reconnects++;
          if (reconnects >= expectedReconnects) {
            client.end();
            done();
          }
        });
      });
    });
    it('shoud not keep requeueing the first message when offline', function (done) {
      this.timeout(2500);

      var server2 = buildServer().listen(port + 45),
      client = mqtt.connect({
            port: port + 45,
            host: 'localhost',
            connectTimeout: 350,
            reconnectPeriod: 300 });

      server2.on('client', function (c) {
        client.publish('hello', 'world', { qos: 1 }, function () {
          c.destroy();
          server2.close();
          client.publish('hello', 'world', { qos: 1 });
        });
      });

      setTimeout(function () {
        if (client.queue.length === 0) {
          client.end(true);
          done();
        } else {
          client.end(true);
        }
      }, 2000);
    });
  });
});
<|endoftext|>


<commit_before>const assert = require('assert')
const assertDir = require('assert-dir-equal')
const Metalsmith = require('metalsmith')
const collections = require('../')

const titles = [
  'Other Article!',
  'Something',
  'Zoo Article'
]

/* global it */
function test(name, numberOfItems = 3) {
  it('should match collections in ' + name, done => {
    const path = 'test/fixtures/' + name
    const metalsmith = new Metalsmith(path)
    metalsmith
      .use(collections())
      .build(function (err) {
        if (err) {
          return done(err)
        }

        // Ensure the collection was loaded correctly.
        const articles = this.metadata().articlelist
        assert.equal(numberOfItems, articles.length)

        if (name === 'multiple') {
          const bikes = this.metadata().bikes
          assert.equal(bikes.length, 2)
        } else {
          // Ensure the titles match.
          articles.forEach((file, i) => {
            if (file) {
              assert.equal(file.title, titles[i])
            }
          })
        }

        // Check whether the files were build just file.
        assertDir(path + '/build', path + '/expected')

        done()
      })
  })
}

test('basic')
test('metadata')
test('multiple', 4)
<commit_msg>Node 4/5<commit_after>const assert = require('assert')
const assertDir = require('assert-dir-equal')
const Metalsmith = require('metalsmith')
const collections = require('../')

const titles = [
  'Other Article!',
  'Something',
  'Zoo Article'
]

/* global it */
function test(name, numberOfItems) {
  if (!numberOfItems) {
    numberOfItems = 3
  }
  it('should match collections in ' + name, done => {
    const path = 'test/fixtures/' + name
    const metalsmith = new Metalsmith(path)
    metalsmith
      .use(collections())
      .build(function (err) {
        if (err) {
          return done(err)
        }

        // Ensure the collection was loaded correctly.
        const articles = this.metadata().articlelist
        assert.equal(numberOfItems, articles.length)

        if (name === 'multiple') {
          const bikes = this.metadata().bikes
          assert.equal(bikes.length, 2)
        } else {
          // Ensure the titles match.
          articles.forEach((file, i) => {
            if (file) {
              assert.equal(file.title, titles[i])
            }
          })
        }

        // Check whether the files were build just file.
        assertDir(path + '/build', path + '/expected')

        done()
      })
  })
}

test('basic')
test('metadata')
test('multiple', 4)
<|endoftext|>