<|start_filename|>tests/unit/components/autocomplete-input-test.js<|end_filename|>
import { moduleForComponent, test } from 'ember-qunit';

moduleForComponent('autocomplete-input', 'Unit | Component | autocomplete input', {
  // Specify the other units that are required for this test
  // needs: ['component:foo', 'helper:bar'],
  unit: true
});

test('it will send a clearSearch action when the esc key is pressed', function(assert) {
  assert.expect(1);

  let component = this.subject();
  component.actions.clearSearch = () => {
    assert.ok(true, 'it sends a clearSearch action');
  };

  component.onEscPress();
});

test('it will not send the selectResult action if  no highlighted result on enter press', function(assert) {
  assert.expect(0);

  let component = this.subject();
  component.set('results', [{
    name: '<NAME>',
    value: 'bombers'
  }, {
    name: '<NAME>',
    value: 'cats'
  }, {
    name: '<NAME>',
    value: 'crows'
  }]);
  component.set('highlightedResultIndex', -1);

  component.actions.selectResult = () => {
    assert.ok(false, 'it will not send the select result action');
  };

  component.onEnterPress();
});

test('it will send the selectResult action on currently highlighted result on enter press', function(assert) {
  assert.expect(2);

  let component = this.subject();
  component.set('results', [{
    name: '<NAME>',
    value: 'bombers'
  }, {
    name: '<NAME>',
    value: 'cats'
  }, {
    name: '<NAME>',
    value: 'crows'
  }]);
  component.set('highlightedResultIndex', 0);

  component.actions.selectResult = (result) => {
    assert.equal(result.name, component.get('results')[component.get('highlightedResultIndex')].name, 'it sends the currently highlighted results');
    assert.equal(result.value, component.get('results')[component.get('highlightedResultIndex')].value, 'it sends the currently highlighted results');
  };

  component.onEnterPress();
});

test('it will move the highlighted index down one on down key action', function(assert) {
  assert.expect(1);

  let component = this.subject();

  component.set('results', [{
    name: '<NAME>',
    value: 'bombers'
  }, {
    name: '<NAME>',
    value: 'cats'
  }, {
    name: '<NAME>',
    value: 'crows'
  }]);
  component.set('highlightedResultIndex', 0);

  component.onDownPress();

  assert.equal(component.get('highlightedResultIndex'), 1);
});

test('it will move the highlighted index back to top down key action when last element highlighted', function(assert) {
  assert.expect(1);

  let component = this.subject();

  component.set('results', [{
    name: '<NAME>',
    value: 'bombers'
  }, {
    name: '<NAME>',
    value: 'cats'
  }, {
    name: '<NAME>',
    value: 'crows'
  }]);
  component.set('highlightedResultIndex', component.get('results').length - 1);

  component.onDownPress();

  assert.equal(component.get('highlightedResultIndex'), 0);
});

test('it will move the highlighted index up one on up key action', function(assert) {
  assert.expect(1);

  let component = this.subject();

  component.set('results', [{
    name: '<NAME>',
    value: 'bombers'
  }, {
    name: '<NAME>',
    value: 'cats'
  }, {
    name: '<NAME>',
    value: 'crows'
  }]);
  component.set('highlightedResultIndex', 2);

  component.onUpPress();

  assert.equal(component.get('highlightedResultIndex'), 1);
});

test('it will move the highlighted index to bottom on up key action when first element highlighted', function(assert) {
  assert.expect(1);

  let component = this.subject();

  component.set('results', [{
    name: '<NAME>',
    value: 'bombers'
  }, {
    name: 'Geelong Cats',
    value: 'cats'
  }, {
    name: '<NAME>',
    value: 'crows'
  }]);
  component.set('highlightedResultIndex', 0);

  component.onUpPress();

  assert.equal(component.get('highlightedResultIndex'), component.get('results').length - 1);
});



<|start_filename|>tests/integration/components/autocomplete-result-item-test.js<|end_filename|>
import Ember from 'ember';
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';

const { Object: EmberObject, defineProperty, computed } = Ember;

moduleForComponent('autocomplete-result-item', 'Integration | Component | autocomplete result item', {
  integration: true
});

test('it renders the result item with default mappings', function(assert) {
  assert.expect(1);

  this.set('result', {
    name: '<NAME>',
    value: 'bombers'
  });
  this.render(hbs`{{autocomplete-result-item result=result resultName='name' resultValue='value'}}`);

  assert.equal(this.$('.autocomplete-result-item').text().trim(), this.get('result').name, 'it displays the autocomplete result correctly');
});

test('it renders the result item with custom mappings', function(assert) {
  assert.expect(1);

  this.set('result', {
    teamName: '<NAME>',
    teamValue: 'bombers'
  });
  this.render(hbs`{{autocomplete-result-item result=result resultName='teamName' resultValue='teamValue'}}`);

  assert.equal(this.$('.autocomplete-result-item').text().trim(), this.get('result').teamName, 'it displays the autocomplete result correctly');
});

test('it renders the result item highlighted with default mappings', function(assert) {
  assert.expect(1);

  this.set('result', {
    name: '<NAME>',
    value: 'bombers'
  });
  this.set('highlightedResult', this.get('result'));
  this.render(hbs`{{autocomplete-result-item result=result highlightedResult=highlightedResult resultName='name' resultValue='value'}}`);

  assert.equal(this.$('.autocomplete-result-item').text().trim(), this.get('result').name, 'it displays the autocomplete result correctly');
});

test('it renders the result item highlighted with custom mappings', function(assert) {
  assert.expect(1);

  this.set('result', {
    teamName: '<NAME>',
    teamValue: 'bombers'
  });
  this.set('highlightedResult', this.get('result'));
  this.render(hbs`{{autocomplete-result-item result=result highlightedResult=highlightedResult resultName='teamName' resultValue='teamValue'}}`);

  assert.equal(this.$('.autocomplete-result-item').text().trim(), this.get('result').teamName, 'it displays the autocomplete result correctly');
});

test('it renders a result item with computed propeties and default mappings', function(assert) {
  assert.expect(1);

  let result = new EmberObject({});
  defineProperty(result, 'name', computed(function() { return '<NAME>'; }));
  defineProperty(result, 'value', computed(function() { return 'bombers'; }));
  this.set('result', result);

  this.render(hbs`{{autocomplete-result-item result=result resultName='name' resultValue='value'}}`);

  assert.equal(this.$('.autocomplete-result-item').text().trim(), this.get('result').get('name'), 'it displays the autocomplete result correctly');
});

test('it renders a result item with computed propeties and custom mappings', function(assert) {
  assert.expect(1);

  let result = new EmberObject({});
  defineProperty(result, 'teamName', computed(function() { return '<NAME>'; }));
  defineProperty(result, 'teamValue', computed(function() { return 'bombers'; }));
  this.set('result', result);

  this.render(hbs`{{autocomplete-result-item result=result resultName='teamName' resultValue='teamValue'}}`);

  assert.equal(this.$('.autocomplete-result-item').text().trim(), this.get('result').get('teamName'), 'it displays the autocomplete result correctly');
});

test('it renders a result item highlighted with computed properties and default mappings', function(assert) {
  assert.expect(1);

  let result = new EmberObject({});
  defineProperty(result, 'name', computed(function() { return '<NAME>'; }));
  defineProperty(result, 'value', computed(function() { return 'bombers'; }));
  this.set('result', result);
  this.set('highlightedResult', this.get('result'));

  this.render(hbs`{{autocomplete-result-item result=result highlightedResult=highlightedResult resultName='name' resultValue='value'}}`);

  assert.equal(this.$('.autocomplete-result-item').text().trim(), this.get('result').get('name'), 'it displays the autocomplete result correctly');
});

test('it renders a result item highlighted with computed properties and custom mappings', function(assert) {
  assert.expect(1);

  let result = new EmberObject({});
  defineProperty(result, 'teamName', computed(function() { return '<NAME>'; }));
  defineProperty(result, 'teamValue', computed(function() { return 'bombers'; }));
  this.set('result', result);
  this.set('highlightedResult', this.get('result'));

  this.render(hbs`{{autocomplete-result-item result=result highlightedResult=highlightedResult resultName='teamName' resultValue='teamValue'}}`);

  assert.equal(this.$('.autocomplete-result-item').text().trim(), this.get('result').get('teamName'), 'it displays the autocomplete result correctly');
});

test('it fires the select result on click with default mappings', function(assert) {
  assert.expect(1);

  this.set('result', {
    name: '<NAME>',
    value: 'bombers'
  });

  this.set('selectResult', (result) => {
    assert.deepEqual(result, this.get('result')), 'it passes the correct result value';
  });
  this.render(hbs`{{autocomplete-result-item result=result selectResult=(action selectResult) resultName='name' resultValue='value'}}`);

  this.$('.autocomplete-result-item').click();
});

test('it fires the select result on click with custom mappings', function(assert) {
  assert.expect(1);

  this.set('result', {
    teamName: '<NAME>',
    teamValue: 'bombers'
  });

  this.set('selectResult', (result) => {
    assert.deepEqual(result, this.get('result')), 'it passes the correct result value';
  });
  this.render(hbs`{{autocomplete-result-item result=result selectResult=(action selectResult) resultName='teamName' resultValue='teamValue'}}`);

  this.$('.autocomplete-result-item').click();
});


<|start_filename|>tests/integration/components/autocomplete-input-test.js<|end_filename|>
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';

moduleForComponent('autocomplete-input', 'Integration | Component | autocomplete input', {
  integration: true
});

test('it renders an input with the correct name and id on input', function(assert) {
  assert.expect(2);

  this.set('selectResult', function() {});

  this.set('results', []);
  this.set('term', 'term');
  this.set('name', 'my-autocomplete-input');

  this.render(hbs`{{autocomplete-input name=name selectResult=(action selectResult) term=term results=results}}`);

  assert.equal(this.$('.autocomplete-input input[type="text"]').attr('name'), this.get('name'), 'it has the correct name');
  assert.equal(this.$('.autocomplete-input input[type="text"]').attr('id'), this.get('name'), 'it has the correct id');
});

test('it renders an input with the correct placeholder', function(assert) {
  assert.expect(1);

  this.set('selectResult', function() {});

  this.set('results', []);
  this.set('placeholder', 'my placeholder text...');

  this.render(hbs`{{autocomplete-input placeholder=placeholder selectResult=(action selectResult) term=term results=results}}`);

  assert.equal(this.$('.autocomplete-input input[type="text"]').attr('placeholder'), this.get('placeholder'), 'it has the correct placeholder');
});

test('it renders an input with the correct autocomplete value', function(assert) {
  assert.expect(1);

  this.set('selectResult', function() {});

  this.set('results', []);
  this.set('autocomplete', 'my-autocomplete-input');

  this.render(hbs`{{autocomplete-input placeholder=placeholder selectResult=(action selectResult) term=term results=results autocomplete=autocomplete}}`);

  assert.equal(this.$('.autocomplete-input input[type="text"]').attr('autocomplete'), this.get('autocomplete'), 'it has the correct autocomplete');
});

test('it renders an input without a block and without results', function(assert) {
  assert.expect(2);

  this.set('selectResult', function() {});

  this.set('results', []);
  this.set('term', 'term');

  this.render(hbs`{{autocomplete-input selectResult=(action selectResult) term=term results=results}}`);

  assert.ok(!!this.$('.autocomplete-input input[type="text"]').val(), this.get('term'));
  assert.equal(this.$('.autocomplete-input .autocomplete-results').length, 0, 'it should not have autocomplete results');
});

test('it renders an input with a block and without results', function(assert) {
  assert.expect(2);

  this.set('selectResult', function() {});

  this.set('results', []);
  this.set('term', 'term');

  this.render(hbs`
    {{#autocomplete-input selectResult=(action selectResult) term=term results=results as |term|}}
      {{input class='my-input' type='text' value=term}}
    {{/autocomplete-input}}
  `);

  assert.ok(!!this.$('.autocomplete-input input.my-input[type="text"]').val(), this.get('term'));
  assert.equal(this.$('.autocomplete-input .autocomplete-results').length, 0, 'it should not have autocomplete results');
});

test('it renders an input without a block and with results', function(assert) {
  assert.expect(4);

  this.set('selectResult', function() {});

  this.set('results', [{
    name: '<NAME>',
    value: 'bombers'
  }, {
    name: '<NAME>',
    value: 'cats'
  }]);
  this.set('term', 'term');

  this.render(hbs`{{autocomplete-input selectResult=(action selectResult) term=term results=results}}`);

  assert.ok(!!this.$('.autocomplete-input input[type="text"]').val(), this.get('term'));
  assert.equal(this.$('.autocomplete-input .autocomplete-results .autocomplete-result-item').length, 2, 'it should have 2 autocomplete result items');
  assert.equal(this.$('.autocomplete-input .autocomplete-results .autocomplete-result-item').eq(0).text().trim(), this.get('results')[0].name, 'it should have correct first result item');
  assert.equal(this.$('.autocomplete-input .autocomplete-results .autocomplete-result-item').eq(1).text().trim(), this.get('results')[1].name, 'it should have correct second result item');
});

test('it renders an input with a block and without results', function(assert) {
  assert.expect(4);

  this.set('selectResult', function() {});

  this.set('results', [{
    name: '<NAME>',
    value: 'bombers'
  }, {
    name: '<NAME>',
    value: 'cats'
  }]);
  this.set('term', 'term');

  this.render(hbs`
    {{#autocomplete-input selectResult=(action selectResult) term=term results=results as |term|}}
      {{input class='my-input' type='text' value=term}}
    {{/autocomplete-input}}
  `);

  assert.ok(!!this.$('.autocomplete-input input[type="text"]').val(), this.get('term'));
  assert.equal(this.$('.autocomplete-input .autocomplete-results .autocomplete-result-item').length, 2, 'it should have 2 autocomplete result items');
  assert.equal(this.$('.autocomplete-input .autocomplete-results .autocomplete-result-item').eq(0).text().trim(), this.get('results')[0].name, 'it should have correct first result item');
  assert.equal(this.$('.autocomplete-input .autocomplete-results .autocomplete-result-item').eq(1).text().trim(), this.get('results')[1].name, 'it should have correct second result item');
});

test('it renders an input with mapped results', function(assert) {
  assert.expect(4);

  this.set('selectResult', function() {});

  this.set('results', [{
    teamName: '<NAME>',
    teamValue: 'bombers'
  }, {
    teamName: '<NAME>',
    teamValue: 'cats'
  }]);
  this.set('term', 'term');

  this.render(hbs`{{autocomplete-input selectResult=(action selectResult) term=term results=results resultName='teamName' resultValue='teamValue'}}`);

  assert.ok(!!this.$('.autocomplete-input input[type="text"]').val(), this.get('term'));
  assert.equal(this.$('.autocomplete-input .autocomplete-results .autocomplete-result-item').length, 2, 'it should have 2 autocomplete result items');
  assert.equal(this.$('.autocomplete-input .autocomplete-results .autocomplete-result-item').eq(0).text().trim(), this.get('results')[0].teamName, 'it should have correct first result item');
  assert.equal(this.$('.autocomplete-input .autocomplete-results .autocomplete-result-item').eq(1).text().trim(), this.get('results')[1].teamName, 'it should have correct second result item');
});

test('it will render a highlighted option depending on the position of the highlight', function(assert) {
  assert.expect(1);

  this.set('selectResult', function() {});

  this.set('results', [{
    name: '<NAME>',
    value: 'bombers'
  }, {
    name: '<NAME>',
    value: 'cats'
  }]);
  this.set('highlightedResultIndex', 0);
  this.set('term', 'term');

  this.render(hbs`{{autocomplete-input selectResult=(action selectResult) highlightedResultIndex=highlightedResultIndex term=term results=results}}`);
  assert.equal(this.$('.autocomplete-input .autocomplete-results .autocomplete-result-item.highlight').text().trim(), this.get('results')[0].name, 'it should have correct highlighted result item');
});

test('it will send an updateTerm action when the term updates if the term is different to the previous term', function(assert) {
  assert.expect(1);

  this.set('results', []);
  this.set('term', 'term');
  this.set('name', 'my-autocomplete-input');

  this.set('selectResult', function() {});
  this.set('updateTerm', function(sentTerm) {
    assert.equal(newTerm, sentTerm, 'it sends through the correct term');
  });

  let newTerm = 'new-term';

  this.render(hbs`{{autocomplete-input name=name updateTerm=(action updateTerm) selectResult=(action selectResult) term=term results=results}}`);

  this.$('.autocomplete-input input[type="text"]').val(newTerm).change();
});

test('it will not send an updateTerm action when the term updates if the term is the same to the previous term', function(assert) {
  assert.expect(0);

  this.set('results', []);
  this.set('term', 'term');
  this.set('name', 'my-autocomplete-input');

  this.set('selectResult', function() {});
  this.set('updateTerm', function() {
    assert.ok(false, 'it should not send this action');
  });

  let newTerm = this.get('term');

  this.render(hbs`{{autocomplete-input name=name updateTerm=(action updateTerm) selectResult=(action selectResult) term=term results=results}}`);

  this.$('.autocomplete-input input[type="text"]').val(newTerm).change();
});

test('it will not send an updateTerm action twice when the term updates with the same term twice', function(assert) {
  assert.expect(1);

  this.set('results', []);
  this.set('term', 'term');
  this.set('name', 'my-autocomplete-input');

  this.set('selectResult', function() {});
  this.set('updateTerm', function(sentTerm) {
    assert.equal(newTerm, sentTerm, 'it sends through the correct term');
  });

  let newTerm = 'new-term';

  this.render(hbs`{{autocomplete-input name=name updateTerm=(action updateTerm) selectResult=(action selectResult) term=term results=results}}`);

  this.$('.autocomplete-input input[type="text"]').val(newTerm).change();
  this.$('.autocomplete-input input[type="text"]').val(newTerm).change();
});


<|start_filename|>app/components/autocomplete-result-item.js<|end_filename|>
export { default } from 'ember-cli-autocomplete-input/components/autocomplete-result-item';

<|start_filename|>app/components/autocomplete-input.js<|end_filename|>
export { default } from 'ember-cli-autocomplete-input/components/autocomplete-input';

<|start_filename|>addon/components/autocomplete-input.js<|end_filename|>
import Ember from 'ember';
import layout from '../templates/components/autocomplete-input';
import KeyboardNavMixin from 'ember-cli-keyboard-nav/mixins/keyboard-nav';

const { Component, computed, observer } = Ember;

export default Component.extend(KeyboardNavMixin, {

  layout,

  // Attributes

  name: '',

  resultName: 'name',

  resultValue: 'value',

  results: [],

  highlightedResultIndex: -1,

  term: '',

  lastTerm: '',

  placeholder: '',

  autocomplete: '',

  // Properties

  highlightedResult: computed('results.[]', 'highlightedResultIndex', function() {
    return this.get('results')[this.get('highlightedResultIndex')];
  }),

  hasResults: computed.notEmpty("results"),

  // Observers

  termDidChange: observer('term', function() {
    this.send('updateTerm', this.get('term'));
  }),

  // Hooks

  didInsertElement() {
    this.bindKeys(this.$('input[type="text"]'));
    this.set('lastTerm', this.get('term'));
  },

  // Keyboard Nav actions

  onEnterPress() {
    let result = this.get("results")[this.get("highlightedResultIndex")];

    if(result) {
      this.send("selectResult", result);
    }
  },

  onEscPress() {
    this.send("clearSearch");
  },

  onDownPress() {
    let index = 0;

    if(this.get("highlightedResultIndex") >= 0) {
      index = this.get("highlightedResultIndex") + 1;
    }

    if(index > this.get("results").length - 1) {
      index = 0;
    }

    this.set("highlightedResultIndex", index);
  },

  onUpPress() {
    let lastItem = this.get("results").length - 1;
    let index = lastItem;

    if(this.get("highlightedResultIndex") >= 0) {
      index = this.get("highlightedResultIndex") - 1;
    }

    if(index < 0) {
      index = lastItem;
    }

    this.set("highlightedResultIndex", index);
  },

  actions: {
    selectResult(result) {
      this.sendAction('selectResult', result);
    },

    updateTerm(term) {
      if (term !== this.get('lastTerm')) {
        this.set('lastTerm', term);
        this.sendAction('updateTerm', term);
      }
    },

    clearSearch() {
      this.sendAction("clearSearch");
    }
  }


});
