Replace jquery-ui.sortable with ember-sortable for nav items

refs #6458, closes #6457
- replaces jquery-ui.sortable with ember-sortable for drag-n-drop handling
- moves the "new/blank" nav item out of the nav items list
  - allows it to be excluded from the draggable list
  - cleans up handling of the `navigationItems` array as there's no longer a need to ignore/exclude this extra item
- clears validation errors when typing in the respective field
- adds acceptance test for adding/removing nav items
- improves acceptance test for saving nav items to cover more edge cases
This commit is contained in:
Kevin Ansfield 2016-02-09 17:16:18 +00:00
parent 4823756e72
commit e0230adae6
12 changed files with 280 additions and 162 deletions

View File

@ -26,8 +26,8 @@ export default TextField.extend({
return this.get('baseUrl') === this.get('value'); return this.get('baseUrl') === this.get('value');
}), }),
fakePlaceholder: computed('isBaseUrl', 'hasFocus', function () { fakePlaceholder: computed('isBaseUrl', 'hasFocus', 'isNew', function () {
return this.get('isBaseUrl') && this.get('last') && !this.get('hasFocus'); return this.get('isBaseUrl') && this.get('isNew') && !this.get('hasFocus');
}), }),
didReceiveAttrs() { didReceiveAttrs() {
@ -72,9 +72,10 @@ export default TextField.extend({
}, },
keyPress(event) { keyPress(event) {
this.attrs.clearErrors();
// enter key // enter key
if (event.keyCode === 13) { if (event.keyCode === 13) {
event.preventDefault();
this.notifyUrlChanged(); this.notifyUrlChanged();
} }

View File

@ -1,15 +1,18 @@
import Ember from 'ember'; import Ember from 'ember';
import ValidationStateMixin from 'ghost/mixins/validation-state'; import ValidationState from 'ghost/mixins/validation-state';
import SortableItem from 'ember-sortable/mixins/sortable-item';
const {Component, computed} = Ember; const {Component, computed, run} = Ember;
const {readOnly} = computed; const {alias, readOnly} = computed;
export default Component.extend(ValidationStateMixin, { export default Component.extend(ValidationState, SortableItem, {
classNames: 'gh-blognav-item', classNames: 'gh-blognav-item',
classNameBindings: ['errorClass'], classNameBindings: ['errorClass', 'navItem.isNew::gh-blognav-item--sortable'],
attributeBindings: ['order:data-order'], new: false,
order: readOnly('navItem.order'), handle: '.gh-blognav-grab',
model: alias('navItem'),
errors: readOnly('navItem.errors'), errors: readOnly('navItem.errors'),
errorClass: computed('hasError', function () { errorClass: computed('hasError', function () {
@ -20,12 +23,12 @@ export default Component.extend(ValidationStateMixin, {
keyPress(event) { keyPress(event) {
// enter key // enter key
if (event.keyCode === 13) { if (event.keyCode === 13 && this.get('navItem.isNew')) {
event.preventDefault(); event.preventDefault();
this.send('addItem'); run.scheduleOnce('actions', this, function () {
this.send('addItem');
});
} }
this.get('navItem.errors').clear();
}, },
actions: { actions: {
@ -39,6 +42,14 @@ export default Component.extend(ValidationStateMixin, {
updateUrl(value) { updateUrl(value) {
this.sendAction('updateUrl', value, this.get('navItem')); this.sendAction('updateUrl', value, this.get('navItem'));
},
clearLabelErrors() {
this.get('navItem.errors').remove('label');
},
clearUrlErrors() {
this.get('navItem.errors').remove('url');
} }
} }
}); });

View File

@ -8,8 +8,7 @@ const {
RSVP, RSVP,
computed, computed,
inject: {service}, inject: {service},
isBlank, isBlank
observer
} = Ember; } = Ember;
const {Errors} = DS; const {Errors} = DS;
const emberA = Ember.A; const emberA = Ember.A;
@ -17,7 +16,7 @@ const emberA = Ember.A;
export const NavItem = Ember.Object.extend(ValidationEngine, { export const NavItem = Ember.Object.extend(ValidationEngine, {
label: '', label: '',
url: '', url: '',
last: false, isNew: false,
validationType: 'navItem', validationType: 'navItem',
@ -44,6 +43,8 @@ export default Controller.extend(SettingsSaveMixin, {
config: service(), config: service(),
notifications: service(), notifications: service(),
newNavItem: null,
blogUrl: computed('config.blogUrl', function () { blogUrl: computed('config.blogUrl', function () {
let url = this.get('config.blogUrl'); let url = this.get('config.blogUrl');
@ -51,8 +52,7 @@ export default Controller.extend(SettingsSaveMixin, {
}), }),
navigationItems: computed('model.navigation', function () { navigationItems: computed('model.navigation', function () {
let lastItem, let navItems;
navItems;
try { try {
navItems = JSON.parse(this.get('model.navigation') || [{}]); navItems = JSON.parse(this.get('model.navigation') || [{}]);
@ -64,38 +64,27 @@ export default Controller.extend(SettingsSaveMixin, {
return NavItem.create(item); return NavItem.create(item);
}); });
lastItem = navItems.get('lastObject');
if (!lastItem || lastItem.get('isComplete')) {
navItems.addObject(NavItem.create({last: true}));
}
return navItems; return navItems;
}), }),
updateLastNavItem: observer('navigationItems.[]', function () { init() {
let navItems = this.get('navigationItems'); this._super(...arguments);
this.set('newNavItem', NavItem.create({isNew: true}));
navItems.forEach((item, index, items) => { },
if (index === (items.length - 1)) {
item.set('last', true);
} else {
item.set('last', false);
}
});
}),
save() { save() {
let navItems = this.get('navigationItems'); let navItems = this.get('navigationItems');
let newNavItem = this.get('newNavItem');
let notifications = this.get('notifications'); let notifications = this.get('notifications');
let navSetting, let validationPromises = [];
validationPromises; let navSetting;
validationPromises = navItems.map((item) => { if (!newNavItem.get('isBlank')) {
if (item.get('last') && item.get('isBlank')) { validationPromises.pushObject(this.send('addItem'));
return; }
}
return item.validate(); navItems.map((item) => {
validationPromises.pushObject(item.validate());
}); });
return RSVP.all(validationPromises).then(() => { return RSVP.all(validationPromises).then(() => {
@ -103,10 +92,6 @@ export default Controller.extend(SettingsSaveMixin, {
let label = item.get('label').trim(); let label = item.get('label').trim();
let url = item.get('url').trim(); let url = item.get('url').trim();
if (item.get('last') && !item.get('isComplete')) {
return null;
}
return {label, url}; return {label, url};
}).compact(); }).compact();
@ -124,17 +109,22 @@ export default Controller.extend(SettingsSaveMixin, {
}); });
}, },
addNewNavItem() {
let navItems = this.get('navigationItems');
let newNavItem = this.get('newNavItem');
newNavItem.set('isNew', false);
navItems.pushObject(newNavItem);
this.set('newNavItem', NavItem.create({isNew: true}));
},
actions: { actions: {
addItem() { addItem() {
let navItems = this.get('navigationItems'); let newNavItem = this.get('newNavItem');
let lastItem = navItems.get('lastObject');
if (lastItem) { return newNavItem.validate().then(() => {
lastItem.validate().then(() => { this.addNewNavItem();
// Add new blank navItem });
navItems.addObject(NavItem.create({last: true}));
});
}
}, },
deleteItem(item) { deleteItem(item) {
@ -147,12 +137,8 @@ export default Controller.extend(SettingsSaveMixin, {
navItems.removeObject(item); navItems.removeObject(item);
}, },
moveItem(index, newIndex) { reorderItems(navItems) {
let navItems = this.get('navigationItems'); this.set('navigationItems', navItems);
let item = navItems.objectAt(index);
navItems.removeAt(index);
navItems.insertAt(newIndex, item);
}, },
updateUrl(url, navItem) { updateUrl(url, navItem) {
@ -161,6 +147,10 @@ export default Controller.extend(SettingsSaveMixin, {
} }
navItem.set('url', url); navItem.set('url', url);
},
reset() {
this.set('newNavItem', NavItem.create({isNew: true}));
} }
} }
}); });

View File

@ -159,7 +159,7 @@ export default function () {
}); });
this.put('/settings/', function (db, request) { this.put('/settings/', function (db, request) {
let newSettings = JSON.parse(request.requestBody); let newSettings = JSON.parse(request.requestBody).settings;
db.settings.remove(); db.settings.remove();
db.settings.insert(newSettings); db.settings.insert(newSettings);

View File

@ -22,6 +22,11 @@ export default AuthenticatedRoute.extend(styleBody, CurrentUserSettings, {
}); });
}, },
setupController() {
this._super(...arguments);
this.get('controller').send('reset');
},
actions: { actions: {
save() { save() {
// since shortcuts are run on the route, we have to signal to the components // since shortcuts are run on the route, we have to signal to the components

View File

@ -83,15 +83,11 @@
background: color(var(--green) lightness(-10%)); background: color(var(--green) lightness(-10%));
} }
.gh-blognav-item:last-child { .gh-blognav-item:not(.gh-blognav-item--sortable) {
padding-left: calc(16px + 20px); padding-left: calc(16px + 20px);
/* icon-grab + nav-item padding) */ /* icon-grab + nav-item padding) */
} }
.gh-blognav-item:last-child .gh-blognav-grab {
display: none;
}
/* Remove space between inputs on smaller screens */ /* Remove space between inputs on smaller screens */
@media (max-width: 800px) { @media (max-width: 800px) {
.gh-blognav-label { .gh-blognav-label {

View File

@ -1,4 +1,4 @@
{{#unless navItem.last}} {{#unless navItem.isNew}}
<span class="gh-blognav-grab icon-grab"> <span class="gh-blognav-grab icon-grab">
<span class="sr-only">Reorder</span> <span class="sr-only">Reorder</span>
</span> </span>
@ -6,16 +6,16 @@
<div class="gh-blognav-line"> <div class="gh-blognav-line">
{{#gh-validation-status-container tagName="span" class="gh-blognav-label" errors=navItem.errors property="label" hasValidated=navItem.hasValidated}} {{#gh-validation-status-container tagName="span" class="gh-blognav-label" errors=navItem.errors property="label" hasValidated=navItem.hasValidated}}
{{gh-trim-focus-input focus=navItem.last placeholder="Label" value=navItem.label}} {{gh-trim-focus-input focus=navItem.last placeholder="Label" value=navItem.label keyPress=(action "clearLabelErrors")}}
{{gh-error-message errors=navItem.errors property="label"}} {{gh-error-message errors=navItem.errors property="label"}}
{{/gh-validation-status-container}} {{/gh-validation-status-container}}
{{#gh-validation-status-container tagName="span" class="gh-blognav-url" errors=navItem.errors property="url" hasValidated=navItem.hasValidated}} {{#gh-validation-status-container tagName="span" class="gh-blognav-url" errors=navItem.errors property="url" hasValidated=navItem.hasValidated}}
{{gh-navitem-url-input baseUrl=baseUrl url=navItem.url last=navItem.last change="updateUrl"}} {{gh-navitem-url-input baseUrl=baseUrl url=navItem.url isNew=navItem.isNew change="updateUrl" clearErrors=(action "clearUrlErrors")}}
{{gh-error-message errors=navItem.errors property="url"}} {{gh-error-message errors=navItem.errors property="url"}}
{{/gh-validation-status-container}} {{/gh-validation-status-container}}
</div> </div>
{{#if navItem.last}} {{#if navItem.isNew}}
<button type="button" class="gh-blognav-add" {{action "addItem"}}> <button type="button" class="gh-blognav-add" {{action "addItem"}}>
<i class="icon-add2"><span class="sr-only">Add</span></i> <i class="icon-add2"><span class="sr-only">Add</span></i>
</button> </button>

View File

@ -1,4 +1,4 @@
{{#gh-navigation moveItem="moveItem"}} <section class="gh-view">
<header class="view-header"> <header class="view-header">
{{#gh-view-title openMobileMenu="openMobileMenu"}}<span>Navigation</span>{{/gh-view-title}} {{#gh-view-title openMobileMenu="openMobileMenu"}}<span>Navigation</span>{{/gh-view-title}}
<section class="view-actions"> <section class="view-actions">
@ -7,10 +7,13 @@
</header> </header>
<section class="view-container"> <section class="view-container">
<form id="settings-navigation" class="gh-blognav js-gh-blognav" novalidate="novalidate"> <form id="settings-navigation" class="gh-blognav" novalidate="novalidate">
{{#each navigationItems as |navItem|}} {{#sortable-group onChange=(action 'reorderItems') as |group|}}
{{gh-navitem navItem=navItem baseUrl=blogUrl addItem="addItem" deleteItem="deleteItem" updateUrl="updateUrl"}} {{#each navigationItems as |navItem|}}
{{/each}} {{gh-navitem navItem=navItem baseUrl=blogUrl addItem="addItem" deleteItem="deleteItem" updateUrl="updateUrl" group=group}}
{{/each}}
{{/sortable-group}}
{{gh-navitem navItem=newNavItem baseUrl=blogUrl addItem="addItem" updateUrl="updateUrl"}}
</form> </form>
</section> </section>
{{/gh-navigation}} </section>

View File

@ -60,12 +60,17 @@ describe('Acceptance: Settings - Navigation', function () {
andThen(function () { andThen(function () {
expect(currentPath()).to.equal('settings.navigation'); expect(currentPath()).to.equal('settings.navigation');
// test has expected number of rows
expect($('.gh-blognav-item').length, 'navigation items count').to.equal(3); // fixtures contain two nav items, check for three rows as we
// should have one extra that's blank
expect(
find('.gh-blognav-item').length,
'navigation items count'
).to.equal(3);
}); });
}); });
it('saves settings', function () { it('saves navigation settings', function () {
visit('/settings/navigation'); visit('/settings/navigation');
fillIn('.gh-blognav-label:first input', 'Test'); fillIn('.gh-blognav-label:first input', 'Test');
fillIn('.gh-blognav-url:first input', '/test'); fillIn('.gh-blognav-url:first input', '/test');
@ -74,14 +79,47 @@ describe('Acceptance: Settings - Navigation', function () {
click('.btn-blue'); click('.btn-blue');
andThen(function () { andThen(function () {
// TODO: Test for successful save here once we have a visual let [navSetting] = server.db.settings.where({key: 'navigation'});
// indication. For now we know the save happened because
// Pretender doesn't complain about an unknown URL expect(navSetting.value).to.equal('[{"label":"Test","url":"/test/"},{"label":"About","url":"/about"}]');
// don't test against .error directly as it will pick up failed // don't test against .error directly as it will pick up failed
// tests "pre.error" elements // tests "pre.error" elements
expect($('span.error').length, 'error fields count').to.equal(0); expect(find('span.error').length, 'error fields count').to.equal(0);
expect($('.gh-alert').length, 'alerts count').to.equal(0); expect(find('.gh-alert').length, 'alerts count').to.equal(0);
expect(find('.response:visible').length, 'validation errors count')
.to.equal(0);
});
});
it('validates new item correctly on save', function () {
visit('/settings/navigation');
click('.btn-blue');
andThen(function () {
expect(
find('.gh-blognav-item').length,
'number of nav items after saving with blank new item'
).to.equal(3);
});
fillIn('.gh-blognav-label:last input', 'Test');
fillIn('.gh-blognav-url:last input', 'http://invalid domain/');
triggerEvent('.gh-blognav-url:last input', 'blur');
click('.btn-blue');
andThen(function () {
expect(
find('.gh-blognav-item').length,
'number of nav items after saving with invalid new item'
).to.equal(3);
expect(
find('.gh-blognav-item:last .response:visible').length,
'number of invalid fields in new item'
).to.equal(1);
}); });
}); });
@ -91,14 +129,105 @@ describe('Acceptance: Settings - Navigation', function () {
triggerEvent('.gh-blognav-label:first input', 'blur'); triggerEvent('.gh-blognav-label:first input', 'blur');
andThen(function () { andThen(function () {
expect($('.gh-blognav-label:first input').val()).to.equal('Test'); expect(find('.gh-blognav-label:first input').val()).to.equal('Test');
}); });
visit('/settings/code-injection'); visit('/settings/code-injection');
visit('/settings/navigation'); visit('/settings/navigation');
andThen(function () { andThen(function () {
expect($('.gh-blognav-label:first input').val()).to.equal('Home'); expect(find('.gh-blognav-label:first input').val()).to.equal('Home');
});
});
it('can add and remove items', function (done) {
visit('/settings/navigation');
click('.gh-blognav-add');
andThen(function () {
expect(
find('.gh-blognav-label:last .response').is(':visible'),
'blank label has validation error'
).to.be.true;
expect(
find('.gh-blognav-url:last .response').is(':visible'),
'blank url has validation error'
).to.be.true;
});
fillIn('.gh-blognav-label:last input', 'New');
triggerEvent('.gh-blognav-label:last input', 'keypress', {});
andThen(function () {
expect(
find('.gh-blognav-label:last .response').is(':visible'),
'label validation is visible after typing'
).to.be.false;
expect(
find('.gh-blognav-url:last .response').is(':visible'),
'blank url still has validation error'
).to.be.true;
});
fillIn('.gh-blognav-url:last input', '/new');
triggerEvent('.gh-blognav-url:last input', 'keypress', {});
triggerEvent('.gh-blognav-url:last input', 'blur');
andThen(function () {
expect(
find('.gh-blognav-url:last .response').is(':visible'),
'url validation is visible after typing'
).to.be.false;
expect(
find('.gh-blognav-url:last input').val()
).to.equal(`${window.location.protocol}//${window.location.host}/new/`);
});
click('.gh-blognav-add');
andThen(function () {
expect(
find('.gh-blognav-item').length,
'number of nav items after successful add'
).to.equal(4);
expect(
find('.gh-blognav-label:last input').val(),
'new item label value after successful add'
).to.be.blank;
expect(
find('.gh-blognav-url:last input').val(),
'new item url value after successful add'
).to.equal(`${window.location.protocol}//${window.location.host}/`);
expect(
find('.gh-blognav-item .response:visible').length,
'number or validation errors shown after successful add'
).to.equal(0);
});
click('.gh-blognav-item:first .gh-blognav-delete');
andThen(function () {
expect(
find('.gh-blognav-item').length,
'number of nav items after successful remove'
).to.equal(3);
});
click('.btn-blue');
andThen(function () {
let [navSetting] = server.db.settings.where({key: 'navigation'});
expect(navSetting.value).to.equal('[{"label":"About","url":"/about"},{"label":"New","url":"/new/"}]');
done();
}); });
}); });
}); });

View File

@ -35,8 +35,8 @@ describeComponent(
expect($item.find('.response:visible').length).to.equal(0); expect($item.find('.response:visible').length).to.equal(0);
}); });
it('doesn\'t show drag handle for last item', function () { it('doesn\'t show drag handle for new items', function () {
this.set('navItem', NavItem.create({label: 'Test', url: '/url', last: true})); this.set('navItem', NavItem.create({label: 'Test', url: '/url', isNew: true}));
this.render(hbs`{{gh-navitem navItem=navItem baseUrl=baseUrl}}`); this.render(hbs`{{gh-navitem navItem=navItem baseUrl=baseUrl}}`);
let $item = this.$('.gh-blognav-item'); let $item = this.$('.gh-blognav-item');
@ -44,8 +44,8 @@ describeComponent(
expect($item.find('.gh-blognav-grab').length).to.equal(0); expect($item.find('.gh-blognav-grab').length).to.equal(0);
}); });
it('shows add button for last item', function () { it('shows add button for new items', function () {
this.set('navItem', NavItem.create({label: 'Test', url: '/url', last: true})); this.set('navItem', NavItem.create({label: 'Test', url: '/url', isNew: true}));
this.render(hbs`{{gh-navitem navItem=navItem baseUrl=baseUrl}}`); this.render(hbs`{{gh-navitem navItem=navItem baseUrl=baseUrl}}`);
let $item = this.$('.gh-blognav-item'); let $item = this.$('.gh-blognav-item');
@ -70,7 +70,7 @@ describeComponent(
}); });
it('triggers add action', function () { it('triggers add action', function () {
this.set('navItem', NavItem.create({label: 'Test', url: '/url', last: true})); this.set('navItem', NavItem.create({label: 'Test', url: '/url', isNew: true}));
let addActionCallCount = 0; let addActionCallCount = 0;
this.on('add', () => { this.on('add', () => {

View File

@ -23,12 +23,15 @@ describeComponent(
// set defaults // set defaults
this.set('baseUrl', currentUrl); this.set('baseUrl', currentUrl);
this.set('url', ''); this.set('url', '');
this.set('isLast', false); this.set('isNew', false);
this.on('clearErrors', function () {
return null;
});
}); });
it('renders correctly with blank url', function () { it('renders correctly with blank url', function () {
this.render(hbs` this.render(hbs`
{{gh-navitem-url-input baseUrl=baseUrl url=url last=isLast change="updateUrl"}} {{gh-navitem-url-input baseUrl=baseUrl url=url isNew=isNew change="updateUrl" clearErrors=(action "clearErrors")}}
`); `);
let $input = this.$('input'); let $input = this.$('input');
@ -40,7 +43,7 @@ describeComponent(
it('renders correctly with relative urls', function () { it('renders correctly with relative urls', function () {
this.set('url', '/about'); this.set('url', '/about');
this.render(hbs` this.render(hbs`
{{gh-navitem-url-input baseUrl=baseUrl url=url last=isLast change="updateUrl"}} {{gh-navitem-url-input baseUrl=baseUrl url=url isNew=isNew change="updateUrl" clearErrors=(action "clearErrors")}}
`); `);
let $input = this.$('input'); let $input = this.$('input');
@ -53,7 +56,7 @@ describeComponent(
it('renders correctly with absolute urls', function () { it('renders correctly with absolute urls', function () {
this.set('url', 'https://example.com:2368/#test'); this.set('url', 'https://example.com:2368/#test');
this.render(hbs` this.render(hbs`
{{gh-navitem-url-input baseUrl=baseUrl url=url last=isLast change="updateUrl"}} {{gh-navitem-url-input baseUrl=baseUrl url=url isNew=isNew change="updateUrl" clearErrors=(action "clearErrors")}}
`); `);
let $input = this.$('input'); let $input = this.$('input');
@ -74,7 +77,7 @@ describeComponent(
it('deletes base URL on backspace', function () { it('deletes base URL on backspace', function () {
this.render(hbs` this.render(hbs`
{{gh-navitem-url-input baseUrl=baseUrl url=url last=isLast change="updateUrl"}} {{gh-navitem-url-input baseUrl=baseUrl url=url isNew=isNew change="updateUrl" clearErrors=(action "clearErrors")}}
`); `);
let $input = this.$('input'); let $input = this.$('input');
@ -90,7 +93,7 @@ describeComponent(
it('deletes base URL on delete', function () { it('deletes base URL on delete', function () {
this.render(hbs` this.render(hbs`
{{gh-navitem-url-input baseUrl=baseUrl url=url last=isLast change="updateUrl"}} {{gh-navitem-url-input baseUrl=baseUrl url=url isNew=isNew change="updateUrl" clearErrors=(action "clearErrors")}}
`); `);
let $input = this.$('input'); let $input = this.$('input');
@ -109,7 +112,7 @@ describeComponent(
return null; return null;
}); });
this.render(hbs` this.render(hbs`
{{gh-navitem-url-input baseUrl=baseUrl url=url last=isLast change="updateUrl"}} {{gh-navitem-url-input baseUrl=baseUrl url=url isNew=isNew change="updateUrl" clearErrors=(action "clearErrors")}}
`); `);
let $input = this.$('input'); let $input = this.$('input');
@ -128,7 +131,7 @@ describeComponent(
return null; return null;
}); });
this.render(hbs` this.render(hbs`
{{gh-navitem-url-input baseUrl=baseUrl url=url last=isLast change="updateUrl"}} {{gh-navitem-url-input baseUrl=baseUrl url=url isNew=isNew change="updateUrl" clearErrors=(action "clearErrors")}}
`); `);
let $input = this.$('input'); let $input = this.$('input');
@ -153,7 +156,7 @@ describeComponent(
return null; return null;
}); });
this.render(hbs` this.render(hbs`
{{gh-navitem-url-input baseUrl=baseUrl url=url last=isLast change="updateUrl"}} {{gh-navitem-url-input baseUrl=baseUrl url=url isNew=isNew change="updateUrl" clearErrors=(action "clearErrors")}}
`); `);
let $input = this.$('input'); let $input = this.$('input');
@ -175,7 +178,7 @@ describeComponent(
return null; return null;
}); });
this.render(hbs` this.render(hbs`
{{gh-navitem-url-input baseUrl=baseUrl url=url last=isLast change="updateUrl"}} {{gh-navitem-url-input baseUrl=baseUrl url=url isNew=isNew change="updateUrl" clearErrors=(action "clearErrors")}}
`); `);
let $input = this.$('input'); let $input = this.$('input');
@ -187,9 +190,9 @@ describeComponent(
}); });
it('toggles .fake-placeholder on focus', function () { it('toggles .fake-placeholder on focus', function () {
this.set('isLast', true); this.set('isNew', true);
this.render(hbs ` this.render(hbs `
{{gh-navitem-url-input baseUrl=baseUrl url=url last=isLast change="updateUrl"}} {{gh-navitem-url-input baseUrl=baseUrl url=url isNew=isNew change="updateUrl" clearErrors=(action "clearErrors")}}
`); `);
let $input = this.$('input'); let $input = this.$('input');
@ -208,7 +211,7 @@ describeComponent(
}); });
this.render(hbs ` this.render(hbs `
{{gh-navitem-url-input baseUrl=baseUrl url=url last=isLast change="updateUrl"}} {{gh-navitem-url-input baseUrl=baseUrl url=url isNew=isNew change="updateUrl" clearErrors=(action "clearErrors")}}
`); `);
let $input = this.$('input'); let $input = this.$('input');
@ -224,7 +227,7 @@ describeComponent(
}); });
this.render(hbs ` this.render(hbs `
{{gh-navitem-url-input baseUrl=baseUrl url=url last=isLast change="updateUrl"}} {{gh-navitem-url-input baseUrl=baseUrl url=url isNew=isNew change="updateUrl" clearErrors=(action "clearErrors")}}
`); `);
let $input = this.$('input'); let $input = this.$('input');
@ -245,7 +248,7 @@ describeComponent(
}); });
this.render(hbs ` this.render(hbs `
{{gh-navitem-url-input baseUrl=baseUrl url=url last=isLast change="updateUrl"}} {{gh-navitem-url-input baseUrl=baseUrl url=url isNew=isNew change="updateUrl" clearErrors=(action "clearErrors")}}
`); `);
let $input = this.$('input'); let $input = this.$('input');
@ -268,7 +271,7 @@ describeComponent(
}); });
this.render(hbs ` this.render(hbs `
{{gh-navitem-url-input baseUrl=baseUrl url=url last=isLast change="updateUrl"}} {{gh-navitem-url-input baseUrl=baseUrl url=url isNew=isNew change="updateUrl" clearErrors=(action "clearErrors")}}
`); `);
let $input = this.$('input'); let $input = this.$('input');
@ -301,7 +304,7 @@ describeComponent(
}); });
this.render(hbs ` this.render(hbs `
{{gh-navitem-url-input baseUrl=baseUrl url=url last=isLast change="updateUrl"}} {{gh-navitem-url-input baseUrl=baseUrl url=url isNew=isNew change="updateUrl" clearErrors=(action "clearErrors")}}
`); `);
let $input = this.$('input'); let $input = this.$('input');
@ -330,7 +333,7 @@ describeComponent(
}); });
this.render(hbs ` this.render(hbs `
{{gh-navitem-url-input baseUrl=baseUrl url=url last=isLast change="updateUrl"}} {{gh-navitem-url-input baseUrl=baseUrl url=url isNew=isNew change="updateUrl" clearErrors=(action "clearErrors")}}
`); `);
let $input = this.$('input'); let $input = this.$('input');
@ -349,7 +352,7 @@ describeComponent(
}); });
this.render(hbs ` this.render(hbs `
{{gh-navitem-url-input baseUrl=baseUrl url=url last=isLast change="updateUrl"}} {{gh-navitem-url-input baseUrl=baseUrl url=url isNew=isNew change="updateUrl" clearErrors=(action "clearErrors")}}
`); `);
let $input = this.$('input'); let $input = this.$('input');
@ -375,7 +378,7 @@ describeComponent(
}); });
this.render(hbs ` this.render(hbs `
{{gh-navitem-url-input baseUrl=baseUrl url=url last=isLast change="updateUrl"}} {{gh-navitem-url-input baseUrl=baseUrl url=url isNew=isNew change="updateUrl" clearErrors=(action "clearErrors")}}
`); `);
let $input = this.$('input'); let $input = this.$('input');
@ -403,7 +406,7 @@ describeComponent(
}); });
this.render(hbs ` this.render(hbs `
{{gh-navitem-url-input baseUrl=baseUrl url=url last=isLast change="updateUrl"}} {{gh-navitem-url-input baseUrl=baseUrl url=url isNew=isNew change="updateUrl" clearErrors=(action "clearErrors")}}
`); `);
let $input = this.$('input'); let $input = this.$('input');
@ -436,7 +439,7 @@ describeComponent(
}); });
this.render(hbs ` this.render(hbs `
{{gh-navitem-url-input baseUrl=baseUrl url=url last=isLast change="updateUrl"}} {{gh-navitem-url-input baseUrl=baseUrl url=url isNew=isNew change="updateUrl" clearErrors=(action "clearErrors")}}
`); `);
let $input = this.$('input'); let $input = this.$('input');
@ -463,7 +466,7 @@ describeComponent(
}); });
this.render(hbs ` this.render(hbs `
{{gh-navitem-url-input baseUrl=baseUrl url=url last=isLast change="updateUrl"}} {{gh-navitem-url-input baseUrl=baseUrl url=url isNew=isNew change="updateUrl" clearErrors=(action "clearErrors")}}
`); `);
let $input = this.$('input'); let $input = this.$('input');

View File

@ -31,55 +31,30 @@ describeModule(
expect(ctrl.get('blogUrl')).to.equal('http://localhost:2368/blog/'); expect(ctrl.get('blogUrl')).to.equal('http://localhost:2368/blog/');
}); });
it('init: creates a new navigation item', function () {
let ctrl = this.subject();
run(() => {
expect(ctrl.get('newNavItem')).to.exist;
expect(ctrl.get('newNavItem.isNew')).to.be.true;
});
});
it('blogUrl: captures config and ensures trailing slash', function () {
let ctrl = this.subject();
ctrl.set('config.blogUrl', 'http://localhost:2368/blog');
expect(ctrl.get('blogUrl')).to.equal('http://localhost:2368/blog/');
});
it('navigationItems: generates list of NavItems', function () { it('navigationItems: generates list of NavItems', function () {
let ctrl = this.subject(); let ctrl = this.subject();
let lastItem;
run(() => { run(() => {
ctrl.set('model', Ember.Object.create({navigation: navSettingJSON})); ctrl.set('model', Ember.Object.create({navigation: navSettingJSON}));
expect(ctrl.get('navigationItems.length')).to.equal(9); expect(ctrl.get('navigationItems.length')).to.equal(8);
expect(ctrl.get('navigationItems.firstObject.label')).to.equal('Home'); expect(ctrl.get('navigationItems.firstObject.label')).to.equal('Home');
expect(ctrl.get('navigationItems.firstObject.url')).to.equal('/'); expect(ctrl.get('navigationItems.firstObject.url')).to.equal('/');
expect(ctrl.get('navigationItems.firstObject.last')).to.be.false; expect(ctrl.get('navigationItems.firstObject.isNew')).to.be.false;
// adds a blank item as last one is complete
lastItem = ctrl.get('navigationItems.lastObject');
expect(lastItem.get('label')).to.equal('');
expect(lastItem.get('url')).to.equal('');
expect(lastItem.get('last')).to.be.true;
});
});
it('navigationItems: adds blank item if navigation setting is empty', function () {
let ctrl = this.subject();
let lastItem;
run(() => {
ctrl.set('model', Ember.Object.create({navigation: null}));
expect(ctrl.get('navigationItems.length')).to.equal(1);
lastItem = ctrl.get('navigationItems.lastObject');
expect(lastItem.get('label')).to.equal('');
expect(lastItem.get('url')).to.equal('');
});
});
it('updateLastNavItem: correctly sets "last" properties', function () {
let ctrl = this.subject();
let item1,
item2;
run(() => {
ctrl.set('model', Ember.Object.create({navigation: navSettingJSON}));
item1 = ctrl.get('navigationItems.lastObject');
expect(item1.get('last')).to.be.true;
ctrl.get('navigationItems').addObject(Ember.Object.create({label: 'Test', url: '/test'}));
item2 = ctrl.get('navigationItems.lastObject');
expect(item2.get('last')).to.be.true;
expect(item1.get('last')).to.be.false;
}); });
}); });
@ -168,15 +143,20 @@ describeModule(
expect(ctrl.get('navigationItems.length')).to.equal(1); expect(ctrl.get('navigationItems.length')).to.equal(1);
ctrl.set('newNavItem.label', 'New');
ctrl.set('newNavItem.url', '/new');
run(() => { run(() => {
ctrl.send('addItem'); ctrl.send('addItem');
}); });
expect(ctrl.get('navigationItems.length')).to.equal(2); expect(ctrl.get('navigationItems.length')).to.equal(2);
expect(ctrl.get('navigationItems.firstObject.last')).to.be.false; expect(ctrl.get('navigationItems.lastObject.label')).to.equal('New');
expect(ctrl.get('navigationItems.lastObject.label')).to.equal(''); expect(ctrl.get('navigationItems.lastObject.url')).to.equal('/new');
expect(ctrl.get('navigationItems.lastObject.url')).to.equal(''); expect(ctrl.get('navigationItems.lastObject.isNew')).to.be.false;
expect(ctrl.get('navigationItems.lastObject.last')).to.be.true; expect(ctrl.get('newNavItem.label')).to.be.blank;
expect(ctrl.get('newNavItem.url')).to.be.blank;
expect(ctrl.get('newNavItem.isNew')).to.be.true;
}); });
it('action - addItem: doesn\'t insert new item if last object is incomplete', function () { it('action - addItem: doesn\'t insert new item if last object is incomplete', function () {
@ -205,7 +185,7 @@ describeModule(
}); });
}); });
it('action - moveItem: updates navigationItems list', function () { it('action - reorderItems: updates navigationItems list', function () {
let ctrl = this.subject(); let ctrl = this.subject();
let navItems = [ let navItems = [
NavItem.create({label: 'First', url: '/first'}), NavItem.create({label: 'First', url: '/first'}),
@ -215,7 +195,7 @@ describeModule(
run(() => { run(() => {
ctrl.set('navigationItems', navItems); ctrl.set('navigationItems', navItems);
expect(ctrl.get('navigationItems').mapBy('label')).to.deep.equal(['First', 'Second']); expect(ctrl.get('navigationItems').mapBy('label')).to.deep.equal(['First', 'Second']);
ctrl.send('moveItem', 1, 0); ctrl.send('reorderItems', navItems.reverseObjects());
expect(ctrl.get('navigationItems').mapBy('label')).to.deep.equal(['Second', 'First']); expect(ctrl.get('navigationItems').mapBy('label')).to.deep.equal(['Second', 'First']);
}); });
}); });