diff --git a/ghost/admin/app/components/gh-navitem-url-input.js b/ghost/admin/app/components/gh-navitem-url-input.js index 502aedd19d..97278bbcc4 100644 --- a/ghost/admin/app/components/gh-navitem-url-input.js +++ b/ghost/admin/app/components/gh-navitem-url-input.js @@ -26,8 +26,8 @@ export default TextField.extend({ return this.get('baseUrl') === this.get('value'); }), - fakePlaceholder: computed('isBaseUrl', 'hasFocus', function () { - return this.get('isBaseUrl') && this.get('last') && !this.get('hasFocus'); + fakePlaceholder: computed('isBaseUrl', 'hasFocus', 'isNew', function () { + return this.get('isBaseUrl') && this.get('isNew') && !this.get('hasFocus'); }), didReceiveAttrs() { @@ -72,9 +72,10 @@ export default TextField.extend({ }, keyPress(event) { + this.attrs.clearErrors(); + // enter key if (event.keyCode === 13) { - event.preventDefault(); this.notifyUrlChanged(); } diff --git a/ghost/admin/app/components/gh-navitem.js b/ghost/admin/app/components/gh-navitem.js index 9f0b3ec283..341ea1a9da 100644 --- a/ghost/admin/app/components/gh-navitem.js +++ b/ghost/admin/app/components/gh-navitem.js @@ -1,15 +1,18 @@ 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 {readOnly} = computed; +const {Component, computed, run} = Ember; +const {alias, readOnly} = computed; -export default Component.extend(ValidationStateMixin, { +export default Component.extend(ValidationState, SortableItem, { classNames: 'gh-blognav-item', - classNameBindings: ['errorClass'], + classNameBindings: ['errorClass', 'navItem.isNew::gh-blognav-item--sortable'], - attributeBindings: ['order:data-order'], - order: readOnly('navItem.order'), + new: false, + handle: '.gh-blognav-grab', + + model: alias('navItem'), errors: readOnly('navItem.errors'), errorClass: computed('hasError', function () { @@ -20,12 +23,12 @@ export default Component.extend(ValidationStateMixin, { keyPress(event) { // enter key - if (event.keyCode === 13) { + if (event.keyCode === 13 && this.get('navItem.isNew')) { event.preventDefault(); - this.send('addItem'); + run.scheduleOnce('actions', this, function () { + this.send('addItem'); + }); } - - this.get('navItem.errors').clear(); }, actions: { @@ -39,6 +42,14 @@ export default Component.extend(ValidationStateMixin, { updateUrl(value) { this.sendAction('updateUrl', value, this.get('navItem')); + }, + + clearLabelErrors() { + this.get('navItem.errors').remove('label'); + }, + + clearUrlErrors() { + this.get('navItem.errors').remove('url'); } } }); diff --git a/ghost/admin/app/controllers/settings/navigation.js b/ghost/admin/app/controllers/settings/navigation.js index 0e7a477f30..4ba1256054 100644 --- a/ghost/admin/app/controllers/settings/navigation.js +++ b/ghost/admin/app/controllers/settings/navigation.js @@ -8,8 +8,7 @@ const { RSVP, computed, inject: {service}, - isBlank, - observer + isBlank } = Ember; const {Errors} = DS; const emberA = Ember.A; @@ -17,7 +16,7 @@ const emberA = Ember.A; export const NavItem = Ember.Object.extend(ValidationEngine, { label: '', url: '', - last: false, + isNew: false, validationType: 'navItem', @@ -44,6 +43,8 @@ export default Controller.extend(SettingsSaveMixin, { config: service(), notifications: service(), + newNavItem: null, + blogUrl: computed('config.blogUrl', function () { let url = this.get('config.blogUrl'); @@ -51,8 +52,7 @@ export default Controller.extend(SettingsSaveMixin, { }), navigationItems: computed('model.navigation', function () { - let lastItem, - navItems; + let navItems; try { navItems = JSON.parse(this.get('model.navigation') || [{}]); @@ -64,38 +64,27 @@ export default Controller.extend(SettingsSaveMixin, { return NavItem.create(item); }); - lastItem = navItems.get('lastObject'); - if (!lastItem || lastItem.get('isComplete')) { - navItems.addObject(NavItem.create({last: true})); - } - return navItems; }), - updateLastNavItem: observer('navigationItems.[]', function () { - let navItems = this.get('navigationItems'); - - navItems.forEach((item, index, items) => { - if (index === (items.length - 1)) { - item.set('last', true); - } else { - item.set('last', false); - } - }); - }), + init() { + this._super(...arguments); + this.set('newNavItem', NavItem.create({isNew: true})); + }, save() { let navItems = this.get('navigationItems'); + let newNavItem = this.get('newNavItem'); let notifications = this.get('notifications'); - let navSetting, - validationPromises; + let validationPromises = []; + let navSetting; - validationPromises = navItems.map((item) => { - if (item.get('last') && item.get('isBlank')) { - return; - } + if (!newNavItem.get('isBlank')) { + validationPromises.pushObject(this.send('addItem')); + } - return item.validate(); + navItems.map((item) => { + validationPromises.pushObject(item.validate()); }); return RSVP.all(validationPromises).then(() => { @@ -103,10 +92,6 @@ export default Controller.extend(SettingsSaveMixin, { let label = item.get('label').trim(); let url = item.get('url').trim(); - if (item.get('last') && !item.get('isComplete')) { - return null; - } - return {label, url}; }).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: { addItem() { - let navItems = this.get('navigationItems'); - let lastItem = navItems.get('lastObject'); + let newNavItem = this.get('newNavItem'); - if (lastItem) { - lastItem.validate().then(() => { - // Add new blank navItem - navItems.addObject(NavItem.create({last: true})); - }); - } + return newNavItem.validate().then(() => { + this.addNewNavItem(); + }); }, deleteItem(item) { @@ -147,12 +137,8 @@ export default Controller.extend(SettingsSaveMixin, { navItems.removeObject(item); }, - moveItem(index, newIndex) { - let navItems = this.get('navigationItems'); - let item = navItems.objectAt(index); - - navItems.removeAt(index); - navItems.insertAt(newIndex, item); + reorderItems(navItems) { + this.set('navigationItems', navItems); }, updateUrl(url, navItem) { @@ -161,6 +147,10 @@ export default Controller.extend(SettingsSaveMixin, { } navItem.set('url', url); + }, + + reset() { + this.set('newNavItem', NavItem.create({isNew: true})); } } }); diff --git a/ghost/admin/app/mirage/config.js b/ghost/admin/app/mirage/config.js index f6c0586e26..825a57b6da 100644 --- a/ghost/admin/app/mirage/config.js +++ b/ghost/admin/app/mirage/config.js @@ -159,7 +159,7 @@ export default function () { }); this.put('/settings/', function (db, request) { - let newSettings = JSON.parse(request.requestBody); + let newSettings = JSON.parse(request.requestBody).settings; db.settings.remove(); db.settings.insert(newSettings); diff --git a/ghost/admin/app/routes/settings/navigation.js b/ghost/admin/app/routes/settings/navigation.js index 48d00a43fa..06875e7bf9 100644 --- a/ghost/admin/app/routes/settings/navigation.js +++ b/ghost/admin/app/routes/settings/navigation.js @@ -22,6 +22,11 @@ export default AuthenticatedRoute.extend(styleBody, CurrentUserSettings, { }); }, + setupController() { + this._super(...arguments); + this.get('controller').send('reset'); + }, + actions: { save() { // since shortcuts are run on the route, we have to signal to the components diff --git a/ghost/admin/app/styles/layouts/settings.css b/ghost/admin/app/styles/layouts/settings.css index 3631d998e0..ac031b5d10 100644 --- a/ghost/admin/app/styles/layouts/settings.css +++ b/ghost/admin/app/styles/layouts/settings.css @@ -83,15 +83,11 @@ background: color(var(--green) lightness(-10%)); } -.gh-blognav-item:last-child { +.gh-blognav-item:not(.gh-blognav-item--sortable) { padding-left: calc(16px + 20px); /* icon-grab + nav-item padding) */ } -.gh-blognav-item:last-child .gh-blognav-grab { - display: none; -} - /* Remove space between inputs on smaller screens */ @media (max-width: 800px) { .gh-blognav-label { diff --git a/ghost/admin/app/templates/components/gh-navitem.hbs b/ghost/admin/app/templates/components/gh-navitem.hbs index ed48c83447..6ecc4ba421 100644 --- a/ghost/admin/app/templates/components/gh-navitem.hbs +++ b/ghost/admin/app/templates/components/gh-navitem.hbs @@ -1,4 +1,4 @@ -{{#unless navItem.last}} +{{#unless navItem.isNew}} Reorder @@ -6,16 +6,16 @@
{{#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-validation-status-container}} {{#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-validation-status-container}}
-{{#if navItem.last}} +{{#if navItem.isNew}} diff --git a/ghost/admin/app/templates/settings/navigation.hbs b/ghost/admin/app/templates/settings/navigation.hbs index 37de0b354c..bd8ac16b01 100644 --- a/ghost/admin/app/templates/settings/navigation.hbs +++ b/ghost/admin/app/templates/settings/navigation.hbs @@ -1,4 +1,4 @@ -{{#gh-navigation moveItem="moveItem"}} +
{{#gh-view-title openMobileMenu="openMobileMenu"}}Navigation{{/gh-view-title}}
@@ -7,10 +7,13 @@
-
- {{#each navigationItems as |navItem|}} - {{gh-navitem navItem=navItem baseUrl=blogUrl addItem="addItem" deleteItem="deleteItem" updateUrl="updateUrl"}} - {{/each}} + + {{#sortable-group onChange=(action 'reorderItems') as |group|}} + {{#each navigationItems as |navItem|}} + {{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"}}
-{{/gh-navigation}} +
diff --git a/ghost/admin/tests/acceptance/settings/navigation-test.js b/ghost/admin/tests/acceptance/settings/navigation-test.js index 83a68a889d..1c9c774a92 100644 --- a/ghost/admin/tests/acceptance/settings/navigation-test.js +++ b/ghost/admin/tests/acceptance/settings/navigation-test.js @@ -60,12 +60,17 @@ describe('Acceptance: Settings - Navigation', function () { andThen(function () { 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'); fillIn('.gh-blognav-label:first input', 'Test'); fillIn('.gh-blognav-url:first input', '/test'); @@ -74,14 +79,47 @@ describe('Acceptance: Settings - Navigation', function () { click('.btn-blue'); andThen(function () { - // TODO: Test for successful save here once we have a visual - // indication. For now we know the save happened because - // Pretender doesn't complain about an unknown URL + let [navSetting] = server.db.settings.where({key: 'navigation'}); + + 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 // tests "pre.error" elements - expect($('span.error').length, 'error fields count').to.equal(0); - expect($('.gh-alert').length, 'alerts count').to.equal(0); + expect(find('span.error').length, 'error fields 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'); 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/navigation'); 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(); }); }); }); diff --git a/ghost/admin/tests/integration/components/gh-navitem-test.js b/ghost/admin/tests/integration/components/gh-navitem-test.js index 55dcb4ed93..79986422f9 100644 --- a/ghost/admin/tests/integration/components/gh-navitem-test.js +++ b/ghost/admin/tests/integration/components/gh-navitem-test.js @@ -35,8 +35,8 @@ describeComponent( expect($item.find('.response:visible').length).to.equal(0); }); - it('doesn\'t show drag handle for last item', function () { - this.set('navItem', NavItem.create({label: 'Test', url: '/url', last: true})); + it('doesn\'t show drag handle for new items', function () { + this.set('navItem', NavItem.create({label: 'Test', url: '/url', isNew: true})); this.render(hbs`{{gh-navitem navItem=navItem baseUrl=baseUrl}}`); let $item = this.$('.gh-blognav-item'); @@ -44,8 +44,8 @@ describeComponent( expect($item.find('.gh-blognav-grab').length).to.equal(0); }); - it('shows add button for last item', function () { - this.set('navItem', NavItem.create({label: 'Test', url: '/url', last: true})); + it('shows add button for new items', function () { + this.set('navItem', NavItem.create({label: 'Test', url: '/url', isNew: true})); this.render(hbs`{{gh-navitem navItem=navItem baseUrl=baseUrl}}`); let $item = this.$('.gh-blognav-item'); @@ -70,7 +70,7 @@ describeComponent( }); 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; this.on('add', () => { diff --git a/ghost/admin/tests/integration/components/gh-navitem-url-input-test.js b/ghost/admin/tests/integration/components/gh-navitem-url-input-test.js index cdf10932b7..e85de750c6 100644 --- a/ghost/admin/tests/integration/components/gh-navitem-url-input-test.js +++ b/ghost/admin/tests/integration/components/gh-navitem-url-input-test.js @@ -23,12 +23,15 @@ describeComponent( // set defaults this.set('baseUrl', currentUrl); this.set('url', ''); - this.set('isLast', false); + this.set('isNew', false); + this.on('clearErrors', function () { + return null; + }); }); it('renders correctly with blank url', function () { 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'); @@ -40,7 +43,7 @@ describeComponent( it('renders correctly with relative urls', function () { this.set('url', '/about'); 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'); @@ -53,7 +56,7 @@ describeComponent( it('renders correctly with absolute urls', function () { this.set('url', 'https://example.com:2368/#test'); 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'); @@ -74,7 +77,7 @@ describeComponent( it('deletes base URL on backspace', function () { 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'); @@ -90,7 +93,7 @@ describeComponent( it('deletes base URL on delete', function () { 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'); @@ -109,7 +112,7 @@ describeComponent( return null; }); 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'); @@ -128,7 +131,7 @@ describeComponent( return null; }); 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'); @@ -153,7 +156,7 @@ describeComponent( return null; }); 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'); @@ -175,7 +178,7 @@ describeComponent( return null; }); 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'); @@ -187,9 +190,9 @@ describeComponent( }); it('toggles .fake-placeholder on focus', function () { - this.set('isLast', true); + this.set('isNew', true); 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'); @@ -208,7 +211,7 @@ describeComponent( }); 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'); @@ -224,7 +227,7 @@ describeComponent( }); 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'); @@ -245,7 +248,7 @@ describeComponent( }); 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'); @@ -268,7 +271,7 @@ describeComponent( }); 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'); @@ -301,7 +304,7 @@ describeComponent( }); 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'); @@ -330,7 +333,7 @@ describeComponent( }); 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'); @@ -349,7 +352,7 @@ describeComponent( }); 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'); @@ -375,7 +378,7 @@ describeComponent( }); 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'); @@ -403,7 +406,7 @@ describeComponent( }); 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'); @@ -436,7 +439,7 @@ describeComponent( }); 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'); @@ -463,7 +466,7 @@ describeComponent( }); 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'); diff --git a/ghost/admin/tests/unit/controllers/settings/navigation-test.js b/ghost/admin/tests/unit/controllers/settings/navigation-test.js index 62bc90814e..59d5499fb7 100644 --- a/ghost/admin/tests/unit/controllers/settings/navigation-test.js +++ b/ghost/admin/tests/unit/controllers/settings/navigation-test.js @@ -31,55 +31,30 @@ describeModule( 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 () { let ctrl = this.subject(); - let lastItem; run(() => { 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.url')).to.equal('/'); - expect(ctrl.get('navigationItems.firstObject.last')).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; + expect(ctrl.get('navigationItems.firstObject.isNew')).to.be.false; }); }); @@ -168,15 +143,20 @@ describeModule( expect(ctrl.get('navigationItems.length')).to.equal(1); + ctrl.set('newNavItem.label', 'New'); + ctrl.set('newNavItem.url', '/new'); + run(() => { ctrl.send('addItem'); }); 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(''); - expect(ctrl.get('navigationItems.lastObject.url')).to.equal(''); - expect(ctrl.get('navigationItems.lastObject.last')).to.be.true; + expect(ctrl.get('navigationItems.lastObject.label')).to.equal('New'); + expect(ctrl.get('navigationItems.lastObject.url')).to.equal('/new'); + expect(ctrl.get('navigationItems.lastObject.isNew')).to.be.false; + 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 () { @@ -205,7 +185,7 @@ describeModule( }); }); - it('action - moveItem: updates navigationItems list', function () { + it('action - reorderItems: updates navigationItems list', function () { let ctrl = this.subject(); let navItems = [ NavItem.create({label: 'First', url: '/first'}), @@ -215,7 +195,7 @@ describeModule( run(() => { ctrl.set('navigationItems', navItems); 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']); }); });