Last active
March 14, 2017 15:45
-
-
Save krivaten/c0cb0d7e746656fb6f08f6174bb5035b to your computer and use it in GitHub Desktop.
ui all the things
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { expect } from 'chai'; | |
| import { describe, it, beforeEach } from 'mocha'; | |
| import { setupComponentTest } from 'ember-mocha'; | |
| import hbs from 'htmlbars-inline-precompile'; | |
| describe('Integration | Component | ui form group', function() { | |
| const LABEL = '[test-id="txtLabel"]'; | |
| const DESCRIPTION = '[test-id="txtDescription"]'; | |
| setupComponentTest('ui-form-group', { | |
| integration: true | |
| }); | |
| beforeEach(function() { | |
| this.set('label', 'Test'); | |
| }); | |
| it('renders base component', function() { | |
| this.render(hbs`{{ui-form-group label=label update=(action (mut value))}}`); | |
| let message = 'Renders with default class'; | |
| expect(this.$('.form-group'), message).to.have.lengthOf(1); | |
| }); | |
| describe('label', function() { | |
| it('renders label and triggers error when not present', function(done) { | |
| this.render(hbs`{{ui-form-group label=label update=(action (mut value))}}`); | |
| let labelSelector = this.$(LABEL); | |
| let message = 'Renders label attribute when one is provided'; | |
| expect(labelSelector, message).to.have.lengthOf(1); | |
| try { | |
| this.set('label', null); | |
| } catch(error) { | |
| message = 'Triggers error when label is null'; | |
| expect(error.name, message).to.eq('Error'); | |
| done(); | |
| } | |
| }); | |
| it('renders correct value in label', function() { | |
| this.render(hbs`{{ui-form-group label=label update=(action (mut value))}}`); | |
| let labelSelector = this.$(LABEL).text().trim(); | |
| let message = `Renders with label value of "Test"`; | |
| expect(labelSelector, message).to.eq('Test'); | |
| }); | |
| it('ties the label to the input', function() { | |
| this.render(hbs`{{ui-form-group label=label update=(action (mut value))}}`); | |
| let labelFor = this.$(LABEL).attr('for'); | |
| let message = 'Renders for attribute on label element'; | |
| expect(labelFor, message).to.match(/^ember(\d{1,10})-input/); | |
| let inputId = this.$('input').attr('id'); | |
| message = 'Renders id attribute on form input'; | |
| expect(inputId, message).to.match(/^ember(\d{1,10})-input/); | |
| message = 'Label for attribute and input id match'; | |
| expect(inputId, message).to.eq(labelFor); | |
| }); | |
| it('properly toggles visiblity of label', function() { | |
| this.set('labelVisible', false); | |
| this.render(hbs`{{ui-form-group label=label update=(action (mut value)) labelVisible=labelVisible}}`); | |
| let labelSelector = this.$(LABEL); | |
| let message = 'Adds sr-only class to label'; | |
| expect(labelSelector.hasClass('sr-only'), message).to.be.true; | |
| this.set('labelVisible', true); | |
| message = 'Does not add sr-only class to label'; | |
| expect(labelSelector.hasClass('sr-only'), message).to.be.false; | |
| }); | |
| it('properly focuses input when label is clicked', function() { | |
| this.render(hbs`{{ui-form-group label=label update=(action (mut value))}}`); | |
| let activeElement = this.$(document.activeElement).get(0); | |
| let message = 'Focuses on body by default'; | |
| expect(activeElement.tagName, message).to.eq('BODY'); | |
| this.$(LABEL).click(); | |
| activeElement = this.$(document.activeElement).get(0); | |
| message = 'Focuses on input when label is clicked'; | |
| expect(activeElement.tagName, message).to.eq('INPUT'); | |
| }); | |
| }); | |
| describe('input', function() { | |
| it('renders form input', function() { | |
| this.render(hbs`{{ui-form-group label=label update=(action (mut value)) value=value}}`); | |
| let inputSelector = this.$('input'); | |
| let message = 'Renders for attribute on label element'; | |
| expect(inputSelector, message).to.have.lengthOf(1); | |
| }); | |
| it('triggers an error if the update attribute is null', function(done) { | |
| this.set('update', true); | |
| this.render(hbs`{{ui-form-group label=label update=update value=value}}`); | |
| try { | |
| this.set('update', null); | |
| } catch(error) { | |
| let message = 'Triggers error when update is null'; | |
| expect(error.name, message).to.eq('Error'); | |
| done(); | |
| } | |
| }); | |
| it('triggers the update action on input', function() { | |
| this.render(hbs`{{ui-form-group label=label update=(action (mut value)) value=value}}`); | |
| let inputSelector = this.$('input'); | |
| inputSelector.val('Boom').trigger('input'); | |
| let message = 'Triggers update action on input'; | |
| expect(this.get('value'), message).to.eq('Boom'); | |
| }); | |
| it('triggers the update action on change', function() { | |
| this.render(hbs`{{ui-form-group label=label update=(action (mut value)) value=value}}`); | |
| let inputSelector = this.$('input'); | |
| inputSelector.val('Boom').trigger('change'); | |
| let message = 'Triggers update action on change'; | |
| expect(this.get('value'), message).to.eq('Boom'); | |
| }); | |
| it('sets a custom input type', function() { | |
| this.render(hbs`{{ui-form-group label=label update=(action (mut value)) inputType='search'}}`); | |
| let inputSelector = this.$('input'); | |
| let message = 'Sets a custom input type'; | |
| expect(inputSelector.attr('type'), message).to.eq('search'); | |
| }); | |
| }); | |
| describe('description', function() { | |
| it('renders description', function() { | |
| this.render(hbs`{{ui-form-group label=label update=(action (mut value)) description=description}}`); | |
| let descriptionSelector = this.$(DESCRIPTION); | |
| let message = 'Does not render description when one is not provided'; | |
| expect(descriptionSelector, message).to.have.lengthOf(0); | |
| this.set('description', 'Test'); | |
| descriptionSelector = this.$(DESCRIPTION); | |
| message = 'Renders description attribute when one is provided'; | |
| expect(descriptionSelector, message).to.have.lengthOf(1); | |
| }); | |
| it('renders correct value in description', function() { | |
| let descriptionValue = 'Test'; | |
| this.set('description', descriptionValue); | |
| this.render(hbs`{{ui-form-group label=label update=(action (mut value)) description=description}}`); | |
| let descriptionSelector = this.$(DESCRIPTION).text().trim(); | |
| let message = `Renders with value of "${descriptionValue}"`; | |
| expect(descriptionSelector, message).to.eq(descriptionValue); | |
| }); | |
| it('ties the description to the input', function() { | |
| this.set('description', 'Test'); | |
| this.render(hbs`{{ui-form-group label=label update=(action (mut value)) description=description}}`); | |
| let descriptionId = this.$(DESCRIPTION).attr('id'); | |
| let message = 'Renders id attribute on description element'; | |
| expect(descriptionId, message).to.match(/^ember(\d{1,10})-description/); | |
| let inputDescribedBy = this.$('input').attr('aria-describedby'); | |
| message = 'Renders describedby on input'; | |
| expect(inputDescribedBy, message).to.match(/^ember(\d{1,10})-description/); | |
| message = 'Description id and input describedby attribute match'; | |
| expect(inputDescribedBy, message).to.eq(descriptionId); | |
| }); | |
| it('properly toggles visiblity of description', function() { | |
| this.set('description', 'Test'); | |
| this.set('descriptionVisible', false); | |
| this.render(hbs`{{ui-form-group label=label update=(action (mut value)) description=description descriptionVisible=descriptionVisible}}`); | |
| let descriptionSelector = this.$(DESCRIPTION); | |
| let message = 'Adds sr-only class to description'; | |
| expect(descriptionSelector.hasClass('sr-only'), message).to.be.true; | |
| this.set('descriptionVisible', true); | |
| message = 'Does not add sr-only class to description'; | |
| expect(descriptionSelector.hasClass('sr-only'), message).to.be.false; | |
| }); | |
| }); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import Ember from 'ember'; | |
| import hbs from 'htmlbars-inline-precompile'; | |
| const { | |
| computed, | |
| get, | |
| guidFor, | |
| assert, | |
| isPresent | |
| } = Ember; | |
| const LABEL_MSG = 'You must include a "label" attribute in all uses of "{{ui-form-group}}" for disabled users. If you want to hide the label visually, you may also provide the attribute "labelVisible=false".'; | |
| const UPDATE_MSG = 'You must pass an "update" attribute in all uses of "{{ui-form-group}}" for the value to be updated. The most common use is "update=(action (mut value))".'; | |
| export default Ember.Component.extend({ | |
| classNames: ['form-group'], | |
| layout: hbs` | |
| {{#if label}} | |
| <label class="{{unless labelVisible 'sr-only'}}" for="{{inputId}}" test-id="txtLabel">{{label}}</label> | |
| {{/if}} | |
| {{component inputComponent | |
| id=inputId | |
| ariaDescribedBy=descriptionId | |
| type=inputType | |
| value=value | |
| update=update | |
| }} | |
| {{#if description}} | |
| <p class="help-block {{unless descriptionVisible 'sr-only'}}" id="{{descriptionId}}" test-id="txtDescription">{{description}}</p> | |
| {{/if}} | |
| `, | |
| labelVisible: true, | |
| description: null, | |
| descriptionVisible: true, | |
| value: null, | |
| inputType: 'text', | |
| verifyPresence: (value, message) => assert(message, isPresent(value)), | |
| label: computed({ | |
| get() { | |
| this.verifyPresence(null, LABEL_MSG); | |
| return null; | |
| }, | |
| set(key, label) { | |
| this.verifyPresence(label, LABEL_MSG); | |
| return label; | |
| } | |
| }), | |
| update: computed({ | |
| get() { | |
| this.verifyPresence(null, UPDATE_MSG); | |
| return null; | |
| }, | |
| set(key, update) { | |
| this.verifyPresence(update, UPDATE_MSG); | |
| return update; | |
| } | |
| }), | |
| inputComponent: computed('inputType', function() { | |
| return 'ui-input'; | |
| }), | |
| uuid: computed(function() { | |
| return guidFor(this); | |
| }), | |
| inputId: computed('id', function() { | |
| const guid = get(this, 'uuid'); | |
| return `${guid}-input`; | |
| }), | |
| descriptionId: computed('description', function() { | |
| const uuid = get(this, 'uuid'); | |
| const hasDescription = !!get(this, 'description'); | |
| return hasDescription ? `${uuid}-description` : undefined; | |
| }) | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import Ember from 'ember'; | |
| import { expect } from 'chai'; | |
| import { describe, it } from 'mocha'; | |
| import { setupComponentTest } from 'ember-mocha'; | |
| import hbs from 'htmlbars-inline-precompile'; | |
| const { run } = Ember; | |
| describe('Integration | Component | ui input', function() { | |
| setupComponentTest('ui-input', { | |
| integration: true | |
| }); | |
| it('renders', function() { | |
| }); | |
| it('defaults the type to "text"', function() { | |
| this.render(hbs`{{ui-input}}`); | |
| let selector = this.$('input').attr('type'); | |
| let message = 'defaults type to "text"'; | |
| expect(selector, message).to.eq('text'); | |
| }); | |
| it('allows a specific type to be provided', function() { | |
| this.render(hbs`{{ui-input type='search'}}`); | |
| let selector = this.$('input').attr('type'); | |
| let message = 'sets the type to "search"'; | |
| expect(selector, message).to.eq('search'); | |
| }); | |
| it('triggers an assertion when the wrong input type is provided', function(done) { | |
| this.set('type', 'text'); | |
| let message = 'trying to render with a bad type throws an error'; | |
| this.render(hbs`{{ui-input type=type}}`); | |
| try { | |
| this.set('type', 'test'); | |
| } catch(error) { | |
| expect(error.name, message).to.eq('Error'); | |
| done(); | |
| } | |
| }); | |
| it('renders with the value as an attribute', function() { | |
| this.set('value', 'Test'); | |
| this.render(hbs`{{ui-input value=value}}`); | |
| let selector = this.$('input').val(); | |
| let message = 'Properly renders with value as an attribute'; | |
| expect(selector, message).to.eq('Test'); | |
| }); | |
| it('renders with the value as a posistionalParam', function() { | |
| this.set('value', 'Test'); | |
| this.render(hbs`{{ui-input value}}`); | |
| let selector = this.$('input').val(); | |
| let message = 'Properly renders with value as a positionalParam'; | |
| expect(selector, message).to.eq('Test'); | |
| }); | |
| it('triggers update when typing in to the input', function() { | |
| this.render(hbs`{{ui-input value=value update=(action (mut value))}}`); | |
| this.$('input').val('Check').trigger('input'); | |
| let message = 'Value is updated to "Check"'; | |
| expect(this.get('value'), message).to.eq('Check'); | |
| }); | |
| it('triggers update when value is changed', function() { | |
| this.render(hbs`{{ui-input value=value update=(action (mut value))}}`); | |
| this.$('input').val('Check').trigger('change'); | |
| let message = 'Value is updated to "Check"'; | |
| expect(this.get('value'), message).to.eq('Check'); | |
| }); | |
| it('keeps the cursor at the correct position', function() { | |
| this.render(hbs`{{ui-input value update=(action (mut value))}}`); | |
| ['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'].forEach((_, index, letters) => { | |
| let part = letters.slice(0, index + 1).join(''); | |
| run(() => this.$('input').val(part).trigger('input')); | |
| let selector = this.$('input').get(0).selectionStart; | |
| let message = 'Cursor is at correct position'; | |
| expect(selector, message).to.eq(index + 1); | |
| }); | |
| }); | |
| it('adds the placeholder attribute', function() { | |
| this.render(hbs`{{ui-input placeholder=placeholder}}`); | |
| let selector = this.$('input'); | |
| let message = 'Does not add placeholder attribute'; | |
| expect(selector.attr('placeholder'), message).to.be.undefined; | |
| this.set('placeholder', 'Test'); | |
| message = 'Adds placeholder attribute'; | |
| expect(selector.attr('placeholder'), message).to.eq('Test'); | |
| }); | |
| it('adds classes to the input', function() { | |
| this.render(hbs`{{ui-input class=class}}`); | |
| let selector = this.$('input'); | |
| let message = 'Does not add any classes'; | |
| expect(selector.attr('class'), message).to.eq('ember-view'); | |
| this.set('class', 'test'); | |
| message = 'Adds class to input'; | |
| expect(selector.attr('class'), message).to.eq('test ember-view'); | |
| }); | |
| it('adds aria-describedby attribute', function() { | |
| this.render(hbs`{{ui-input ariaDescribedBy=ariaDescribedBy}}`); | |
| let selector = this.$('input'); | |
| let message = 'Does not add aria-describedby attribute'; | |
| expect(selector.attr('aria-describedby'), message).to.be.undefined; | |
| this.set('ariaDescribedBy', 'test'); | |
| message = 'Adds aria-describedby attribute'; | |
| expect(selector.attr('aria-describedby'), message).to.eq('test'); | |
| }); | |
| it('adds aria-labelledby attribute', function() { | |
| this.render(hbs`{{ui-input ariaLabelledBy=ariaLabelledBy}}`); | |
| let selector = this.$('input'); | |
| let message = 'Does not add aria-labelledby attribute'; | |
| expect(selector.attr('aria-labelledby'), message).to.be.undefined; | |
| this.set('ariaLabelledBy', 'test'); | |
| message = 'Adds aria-labelledby attribute'; | |
| expect(selector.attr('aria-labelledby'), message).to.eq('test'); | |
| }); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import Ember from 'ember'; | |
| const { | |
| Component, | |
| computed, | |
| get, | |
| isNone, | |
| assert, | |
| run: { | |
| schedule | |
| } | |
| } = Ember; | |
| const ALLOWED_TYPES = ['text', 'search', 'tel', 'password', 'num']; | |
| const UiInputComponent = Component.extend({ | |
| tagName: 'input', | |
| attributeBindings: [ | |
| 'type', | |
| 'value', | |
| 'placeholder', | |
| 'ariaDescribedBy:aria-describedby', | |
| 'ariaLabelledBy:aria-labelledby' | |
| ], | |
| value: null, | |
| ariaDescribedBy: null, | |
| ariaLabelledBy: null, | |
| change(event) { | |
| this._processNewValue(event.target.value); | |
| }, | |
| input(event) { | |
| this._processNewValue(event.target.value); | |
| }, | |
| _processNewValue(value) { | |
| if (get(this, '_value') !== value) { | |
| this.sendAction('update', value); | |
| } | |
| schedule('afterRender', this, '_syncValue'); | |
| }, | |
| _syncValue() { | |
| if (this.isDestroyed || this.isDestroying) { | |
| return; | |
| } | |
| let actualValue = get(this, '_value'); | |
| let renderedValue = this.readDOMAttr('value'); | |
| if (!isNone(actualValue) && !isNone(renderedValue) && actualValue.toString() !== renderedValue.toString()) { | |
| let element = this.$(); | |
| let rawElement = element.get(0); | |
| let start; | |
| let end; | |
| try { | |
| start = rawElement.selectionStart; | |
| end = rawElement.selectionEnd; | |
| } catch(e) { | |
| // no-op | |
| } | |
| element.val(actualValue); | |
| try { | |
| rawElement.setSelectionRange(start, end); | |
| } catch(e) { | |
| // no-op | |
| } | |
| } | |
| }, | |
| type: computed({ | |
| get() { | |
| return 'text'; | |
| }, | |
| set(key, type) { | |
| assert(`The {{ui-input}} component does not support type="${type}".`, ALLOWED_TYPES.indexOf(type) !== -1); | |
| return type; | |
| } | |
| }) | |
| }); | |
| UiInputComponent.reopenClass({ | |
| positionalParams: ['value'] | |
| }); | |
| export default UiInputComponent; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment