🎨 Added confirmation dialogs when leaving screens with unsaved changes (#891)
closes TryGhost/Ghost#9119, refs TryGhost/Ghost#8483 - Apps - AMP - Added `leave-settings-modal` component to Settings - Apps - AMP - Apps - Slack - Added `leave-settings-modal` component to Settings - Apps - Slack - Added a `triggerDirtyState` action that will uses a new Array with the input data to trigger the dirty state on the parent settings model - Apps - Unsplash - Added `leave-settings-modal` component to Settings - Apps - Unsplash - Used manual tracking of changes with using a custom `dirtyAttributes` property and a `rollbackValue` to manually rollback the `isActive` attribute on the model - Code injection - Added `leave-settings-modal` component to Settings - Code injection - Design - Added `leave-settings-modal` component to Settings - Design (only for navigation model) - Used manual tracking of changes with using a custom `dirtyAttributes` - Added an additional `updateLabel` action to underlying `gh-navitem` component which gets fired on the `focusOut` event, to detect changes on the label - Team - User - Added `leave-settings-modal` component to Team - User - Used manual tracking of changes with using a custom `dirtyAttributes` to track changes in slug and role properties
This commit is contained in:
parent
1e73e5930b
commit
6ef4c622ad
|
@ -42,6 +42,10 @@ export default Component.extend(ValidationState, {
|
|||
this.sendAction('updateUrl', value, this.get('navItem'));
|
||||
},
|
||||
|
||||
updateLabel(value) {
|
||||
this.sendAction('updateLabel', value, this.get('navItem'));
|
||||
},
|
||||
|
||||
clearLabelErrors() {
|
||||
this.get('navItem.errors').remove('label');
|
||||
},
|
||||
|
|
|
@ -9,6 +9,8 @@ export default Controller.extend({
|
|||
|
||||
model: alias('settings.amp'),
|
||||
|
||||
leaveSettingsTransition: null,
|
||||
|
||||
save: task(function* () {
|
||||
let amp = this.get('model');
|
||||
let settings = this.get('settings');
|
||||
|
@ -17,7 +19,6 @@ export default Controller.extend({
|
|||
|
||||
try {
|
||||
return yield settings.save();
|
||||
|
||||
} catch (error) {
|
||||
this.get('notifications').showAPIError(error);
|
||||
throw error;
|
||||
|
@ -31,6 +32,45 @@ export default Controller.extend({
|
|||
|
||||
save() {
|
||||
this.get('save').perform();
|
||||
},
|
||||
|
||||
toggleLeaveSettingsModal(transition) {
|
||||
let leaveTransition = this.get('leaveSettingsTransition');
|
||||
|
||||
if (!transition && this.get('showLeaveSettingsModal')) {
|
||||
this.set('leaveSettingsTransition', null);
|
||||
this.set('showLeaveSettingsModal', false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!leaveTransition || transition.targetName === leaveTransition.targetName) {
|
||||
this.set('leaveSettingsTransition', transition);
|
||||
|
||||
// if a save is running, wait for it to finish then transition
|
||||
if (this.get('save.isRunning')) {
|
||||
return this.get('save.last').then(() => {
|
||||
transition.retry();
|
||||
});
|
||||
}
|
||||
|
||||
// we genuinely have unsaved data, show the modal
|
||||
this.set('showLeaveSettingsModal', true);
|
||||
}
|
||||
},
|
||||
|
||||
leaveSettings() {
|
||||
let transition = this.get('leaveSettingsTransition');
|
||||
let settings = this.get('settings');
|
||||
|
||||
if (!transition) {
|
||||
this.get('notifications').showAlert('Sorry, there was an error in the application. Please let the Ghost team know what happened.', {type: 'error'});
|
||||
return;
|
||||
}
|
||||
|
||||
// roll back changes on model props
|
||||
settings.rollbackAttributes();
|
||||
|
||||
return transition.retry();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Controller from '@ember/controller';
|
||||
import {alias} from '@ember/object/computed';
|
||||
import boundOneWay from 'ghost-admin/utils/bound-one-way';
|
||||
import {empty} from '@ember/object/computed';
|
||||
import {isInvalidError} from 'ember-ajax/errors';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
@ -11,18 +11,23 @@ export default Controller.extend({
|
|||
notifications: service(),
|
||||
settings: service(),
|
||||
|
||||
model: alias('settings.slack.firstObject'),
|
||||
model: boundOneWay('settings.slack.firstObject'),
|
||||
testNotificationDisabled: empty('model.url'),
|
||||
|
||||
leaveSettingsTransition: null,
|
||||
slackArray: [],
|
||||
|
||||
save: task(function* () {
|
||||
let slack = this.get('model');
|
||||
let settings = this.get('settings');
|
||||
let slackArray = this.get('slackArray');
|
||||
|
||||
try {
|
||||
yield slack.validate();
|
||||
settings.get('slack').clear().pushObject(slack);
|
||||
// clear existing objects in slackArray to make sure we only push the validated one
|
||||
slackArray.clear().pushObject(slack);
|
||||
yield settings.set('slack', slackArray);
|
||||
return yield settings.save();
|
||||
|
||||
} catch (error) {
|
||||
if (error) {
|
||||
this.get('notifications').showAPIError(error);
|
||||
|
@ -40,7 +45,6 @@ export default Controller.extend({
|
|||
yield this.get('ajax').post(slackApi);
|
||||
notifications.showNotification('Check your Slack channel for the test message!', {type: 'info', key: 'slack-test.send.success'});
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
notifications.showAPIError(error, {key: 'slack-test:send'});
|
||||
|
||||
|
@ -58,6 +62,59 @@ export default Controller.extend({
|
|||
updateURL(value) {
|
||||
this.set('model.url', value);
|
||||
this.get('model.errors').clear();
|
||||
},
|
||||
|
||||
triggerDirtyState() {
|
||||
let slack = this.get('model');
|
||||
let slackArray = this.get('slackArray');
|
||||
let settings = this.get('settings');
|
||||
|
||||
// Hack to trigger the `isDirty` state on the settings model by setting a new Array
|
||||
// for slack rather that replacing the existing one which would still point to the
|
||||
// same reference and therfore not setting the model into a dirty state
|
||||
slackArray.clear().pushObject(slack);
|
||||
settings.set('slack', slackArray);
|
||||
},
|
||||
|
||||
toggleLeaveSettingsModal(transition) {
|
||||
let leaveTransition = this.get('leaveSettingsTransition');
|
||||
|
||||
if (!transition && this.get('showLeaveSettingsModal')) {
|
||||
this.set('leaveSettingsTransition', null);
|
||||
this.set('showLeaveSettingsModal', false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!leaveTransition || transition.targetName === leaveTransition.targetName) {
|
||||
this.set('leaveSettingsTransition', transition);
|
||||
|
||||
// if a save is running, wait for it to finish then transition
|
||||
if (this.get('save.isRunning')) {
|
||||
return this.get('save.last').then(() => {
|
||||
transition.retry();
|
||||
});
|
||||
}
|
||||
|
||||
// we genuinely have unsaved data, show the modal
|
||||
this.set('showLeaveSettingsModal', true);
|
||||
}
|
||||
},
|
||||
|
||||
leaveSettings() {
|
||||
let transition = this.get('leaveSettingsTransition');
|
||||
let settings = this.get('settings');
|
||||
let slackArray = this.get('slackArray');
|
||||
|
||||
if (!transition) {
|
||||
this.get('notifications').showAlert('Sorry, there was an error in the application. Please let the Ghost team know what happened.', {type: 'error'});
|
||||
return;
|
||||
}
|
||||
|
||||
// roll back changes on model props
|
||||
settings.rollbackAttributes();
|
||||
slackArray.clear();
|
||||
|
||||
return transition.retry();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -8,6 +8,10 @@ export default Controller.extend({
|
|||
settings: service(),
|
||||
|
||||
model: alias('settings.unsplash'),
|
||||
dirtyAttributes: null,
|
||||
rollbackValue: null,
|
||||
|
||||
leaveSettingsTransition: null,
|
||||
|
||||
save: task(function* () {
|
||||
let unsplash = this.get('model');
|
||||
|
@ -15,6 +19,8 @@ export default Controller.extend({
|
|||
|
||||
try {
|
||||
settings.set('unsplash', unsplash);
|
||||
this.set('dirtyAttributes', false);
|
||||
this.set('rollbackValue', null);
|
||||
return yield settings.save();
|
||||
} catch (error) {
|
||||
if (error) {
|
||||
|
@ -30,7 +36,51 @@ export default Controller.extend({
|
|||
},
|
||||
|
||||
update(value) {
|
||||
if (!this.get('dirtyAttributes')) {
|
||||
this.set('rollbackValue', this.get('model.isActive'));
|
||||
}
|
||||
this.set('model.isActive', value);
|
||||
this.set('dirtyAttributes', true);
|
||||
},
|
||||
|
||||
toggleLeaveSettingsModal(transition) {
|
||||
let leaveTransition = this.get('leaveSettingsTransition');
|
||||
|
||||
if (!transition && this.get('showLeaveSettingsModal')) {
|
||||
this.set('leaveSettingsTransition', null);
|
||||
this.set('showLeaveSettingsModal', false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!leaveTransition || transition.targetName === leaveTransition.targetName) {
|
||||
this.set('leaveSettingsTransition', transition);
|
||||
|
||||
// if a save is running, wait for it to finish then transition
|
||||
if (this.get('save.isRunning')) {
|
||||
return this.get('save.last').then(() => {
|
||||
transition.retry();
|
||||
});
|
||||
}
|
||||
|
||||
// we genuinely have unsaved data, show the modal
|
||||
this.set('showLeaveSettingsModal', true);
|
||||
}
|
||||
},
|
||||
|
||||
leaveSettings() {
|
||||
let transition = this.get('leaveSettingsTransition');
|
||||
|
||||
if (!transition) {
|
||||
this.get('notifications').showAlert('Sorry, there was an error in the application. Please let the Ghost team know what happened.', {type: 'error'});
|
||||
return;
|
||||
}
|
||||
|
||||
// roll back changes on model props
|
||||
this.set('model.isActive', this.get('rollbackValue'));
|
||||
this.set('dirtyAttributes', false);
|
||||
this.set('rollbackValue', null);
|
||||
|
||||
return transition.retry();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -19,6 +19,46 @@ export default Controller.extend({
|
|||
actions: {
|
||||
save() {
|
||||
this.get('save').perform();
|
||||
},
|
||||
|
||||
toggleLeaveSettingsModal(transition) {
|
||||
let leaveTransition = this.get('leaveSettingsTransition');
|
||||
|
||||
if (!transition && this.get('showLeaveSettingsModal')) {
|
||||
this.set('leaveSettingsTransition', null);
|
||||
this.set('showLeaveSettingsModal', false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!leaveTransition || transition.targetName === leaveTransition.targetName) {
|
||||
this.set('leaveSettingsTransition', transition);
|
||||
|
||||
// if a save is running, wait for it to finish then transition
|
||||
if (this.get('save.isRunning')) {
|
||||
return this.get('save.last').then(() => {
|
||||
transition.retry();
|
||||
});
|
||||
}
|
||||
|
||||
// we genuinely have unsaved data, show the modal
|
||||
this.set('showLeaveSettingsModal', true);
|
||||
}
|
||||
},
|
||||
|
||||
leaveSettings() {
|
||||
let transition = this.get('leaveSettingsTransition');
|
||||
let settings = this.get('model');
|
||||
|
||||
if (!transition) {
|
||||
this.get('notifications').showAlert('Sorry, there was an error in the application. Please let the Ghost team know what happened.', {type: 'error'});
|
||||
return;
|
||||
}
|
||||
|
||||
// roll back changes on model props
|
||||
settings.rollbackAttributes();
|
||||
|
||||
return transition.retry();
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
|
|
@ -17,6 +17,8 @@ export default Controller.extend({
|
|||
|
||||
newNavItem: null,
|
||||
|
||||
dirtyAttributes: false,
|
||||
|
||||
themes: null,
|
||||
themeToDelete: null,
|
||||
showDeleteThemeModal: notEmpty('themeToDelete'),
|
||||
|
@ -48,6 +50,7 @@ export default Controller.extend({
|
|||
|
||||
try {
|
||||
yield RSVP.all(validationPromises);
|
||||
this.set('dirtyAttributes', false);
|
||||
return yield this.get('model').save();
|
||||
} catch (error) {
|
||||
if (error) {
|
||||
|
@ -63,6 +66,7 @@ export default Controller.extend({
|
|||
|
||||
newNavItem.set('isNew', false);
|
||||
navItems.pushObject(newNavItem);
|
||||
this.set('dirtyAttributes', true);
|
||||
this.set('newNavItem', NavigationItem.create({isNew: true}));
|
||||
$('.gh-blognav-line:last input:first').focus();
|
||||
},
|
||||
|
@ -110,6 +114,16 @@ export default Controller.extend({
|
|||
let navItems = this.get('model.navigation');
|
||||
|
||||
navItems.removeObject(item);
|
||||
this.set('dirtyAttributes', true);
|
||||
},
|
||||
|
||||
updateLabel(label, navItem) {
|
||||
if (!navItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
navItem.set('label', label);
|
||||
this.set('dirtyAttributes', true);
|
||||
},
|
||||
|
||||
updateUrl(url, navItem) {
|
||||
|
@ -118,6 +132,47 @@ export default Controller.extend({
|
|||
}
|
||||
|
||||
navItem.set('url', url);
|
||||
this.set('dirtyAttributes', true);
|
||||
},
|
||||
|
||||
toggleLeaveSettingsModal(transition) {
|
||||
let leaveTransition = this.get('leaveSettingsTransition');
|
||||
|
||||
if (!transition && this.get('showLeaveSettingsModal')) {
|
||||
this.set('leaveSettingsTransition', null);
|
||||
this.set('showLeaveSettingsModal', false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!leaveTransition || transition.targetName === leaveTransition.targetName) {
|
||||
this.set('leaveSettingsTransition', transition);
|
||||
|
||||
// if a save is running, wait for it to finish then transition
|
||||
if (this.get('save.isRunning')) {
|
||||
return this.get('save.last').then(() => {
|
||||
transition.retry();
|
||||
});
|
||||
}
|
||||
|
||||
// we genuinely have unsaved data, show the modal
|
||||
this.set('showLeaveSettingsModal', true);
|
||||
}
|
||||
},
|
||||
|
||||
leaveSettings() {
|
||||
let transition = this.get('leaveSettingsTransition');
|
||||
let model = this.get('model');
|
||||
|
||||
if (!transition) {
|
||||
this.get('notifications').showAlert('Sorry, there was an error in the application. Please let the Ghost team know what happened.', {type: 'error'});
|
||||
return;
|
||||
}
|
||||
|
||||
// roll back changes on model props
|
||||
model.rollbackAttributes();
|
||||
this.set('dirtyAttributes', false);
|
||||
|
||||
return transition.retry();
|
||||
},
|
||||
|
||||
activateTheme(theme) {
|
||||
|
|
|
@ -14,6 +14,8 @@ import {task, taskGroup} from 'ember-concurrency';
|
|||
const {Handlebars} = Ember;
|
||||
|
||||
export default Controller.extend({
|
||||
leaveSettingsTransition: null,
|
||||
dirtyAttributes: false,
|
||||
showDeleteUserModal: false,
|
||||
showSuspendUserModal: false,
|
||||
showTransferOwnerModal: false,
|
||||
|
@ -110,7 +112,7 @@ export default Controller.extend({
|
|||
saveHandlers: taskGroup().enqueue(),
|
||||
|
||||
updateSlug: task(function* (newSlug) {
|
||||
let slug = this.get('model.slug');
|
||||
let slug = this.get('user.slug');
|
||||
|
||||
newSlug = newSlug || slug;
|
||||
newSlug = newSlug.trim();
|
||||
|
@ -151,6 +153,8 @@ export default Controller.extend({
|
|||
}
|
||||
|
||||
this.set('slugValue', serverSlug);
|
||||
this.set('dirtyAttributes', true);
|
||||
|
||||
return true;
|
||||
}).group('saveHandlers'),
|
||||
|
||||
|
@ -181,6 +185,7 @@ export default Controller.extend({
|
|||
window.history.replaceState({path: newPath}, '', newPath);
|
||||
}
|
||||
|
||||
this.set('dirtyAttributes', false);
|
||||
this.get('notifications').closeAlerts('user.update');
|
||||
|
||||
return model;
|
||||
|
@ -195,7 +200,8 @@ export default Controller.extend({
|
|||
|
||||
actions: {
|
||||
changeRole(newRole) {
|
||||
this.set('model.role', newRole);
|
||||
this.get('user').set('role', newRole);
|
||||
this.set('dirtyAttributes', true);
|
||||
},
|
||||
|
||||
deleteUser() {
|
||||
|
@ -213,7 +219,7 @@ export default Controller.extend({
|
|||
},
|
||||
|
||||
suspendUser() {
|
||||
this.get('model').set('status', 'inactive');
|
||||
this.get('user').set('status', 'inactive');
|
||||
return this.get('save').perform();
|
||||
},
|
||||
|
||||
|
@ -224,7 +230,7 @@ export default Controller.extend({
|
|||
},
|
||||
|
||||
unsuspendUser() {
|
||||
this.get('model').set('status', 'active');
|
||||
this.get('user').set('status', 'active');
|
||||
return this.get('save').perform();
|
||||
},
|
||||
|
||||
|
@ -286,13 +292,10 @@ export default Controller.extend({
|
|||
this.get('user.errors').remove('facebook');
|
||||
this.get('user.hasValidated').pushObject('facebook');
|
||||
|
||||
// User input is validated
|
||||
this.get('save').perform().then(() => {
|
||||
// necessary to update the value in the input field
|
||||
this.set('user.facebook', '');
|
||||
run.schedule('afterRender', this, function () {
|
||||
this.set('user.facebook', newUrl);
|
||||
});
|
||||
// necessary to update the value in the input field
|
||||
this.set('user.facebook', '');
|
||||
run.schedule('afterRender', this, function () {
|
||||
this.set('user.facebook', newUrl);
|
||||
});
|
||||
} else {
|
||||
errMessage = 'The URL must be in a format like '
|
||||
|
@ -351,13 +354,10 @@ export default Controller.extend({
|
|||
this.get('user.errors').remove('twitter');
|
||||
this.get('user.hasValidated').pushObject('twitter');
|
||||
|
||||
// User input is validated
|
||||
this.get('save').perform().then(() => {
|
||||
// necessary to update the value in the input field
|
||||
this.set('user.twitter', '');
|
||||
run.schedule('afterRender', this, function () {
|
||||
this.set('user.twitter', newUrl);
|
||||
});
|
||||
// necessary to update the value in the input field
|
||||
this.set('user.twitter', '');
|
||||
run.schedule('afterRender', this, function () {
|
||||
this.set('user.twitter', newUrl);
|
||||
});
|
||||
} else {
|
||||
errMessage = 'The URL must be in a format like '
|
||||
|
@ -398,6 +398,50 @@ export default Controller.extend({
|
|||
});
|
||||
},
|
||||
|
||||
toggleLeaveSettingsModal(transition) {
|
||||
let leaveTransition = this.get('leaveSettingsTransition');
|
||||
|
||||
if (!transition && this.get('showLeaveSettingsModal')) {
|
||||
this.set('leaveSettingsTransition', null);
|
||||
this.set('showLeaveSettingsModal', false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!leaveTransition || transition.targetName === leaveTransition.targetName) {
|
||||
this.set('leaveSettingsTransition', transition);
|
||||
|
||||
// if a save is running, wait for it to finish then transition
|
||||
if (this.get('saveHandlers.isRunning')) {
|
||||
return this.get('saveHandlers.last').then(() => {
|
||||
transition.retry();
|
||||
});
|
||||
}
|
||||
|
||||
// we genuinely have unsaved data, show the modal
|
||||
this.set('showLeaveSettingsModal', true);
|
||||
}
|
||||
},
|
||||
|
||||
leaveSettings() {
|
||||
let transition = this.get('leaveSettingsTransition');
|
||||
let user = this.get('user');
|
||||
|
||||
if (!transition) {
|
||||
this.get('notifications').showAlert('Sorry, there was an error in the application. Please let the Ghost team know what happened.', {type: 'error'});
|
||||
return;
|
||||
}
|
||||
|
||||
// roll back changes on model props
|
||||
user.rollbackAttributes();
|
||||
// roll back the slugValue property
|
||||
if (this.get('dirtyAttributes')) {
|
||||
this.set('slugValue', user.get('slug'));
|
||||
this.set('dirtyAttributes', false);
|
||||
}
|
||||
|
||||
return transition.retry();
|
||||
},
|
||||
|
||||
toggleTransferOwnerModal() {
|
||||
if (this.get('canMakeOwner')) {
|
||||
this.toggleProperty('showTransferOwnerModal');
|
||||
|
|
|
@ -9,6 +9,18 @@ export default AuthenticatedRoute.extend(styleBody, {
|
|||
actions: {
|
||||
save() {
|
||||
this.get('controller').send('save');
|
||||
},
|
||||
|
||||
willTransition(transition) {
|
||||
let controller = this.get('controller');
|
||||
let settings = controller.get('settings');
|
||||
let modelIsDirty = settings.get('hasDirtyAttributes');
|
||||
|
||||
if (modelIsDirty) {
|
||||
transition.abort();
|
||||
controller.send('toggleLeaveSettingsModal', transition);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,33 @@
|
|||
import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
|
||||
import styleBody from 'ghost-admin/mixins/style-body';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
export default AuthenticatedRoute.extend(styleBody, {
|
||||
titleToken: 'Settings - Apps - Slack',
|
||||
|
||||
classNames: ['settings-view-apps-slack'],
|
||||
|
||||
settings: service(),
|
||||
|
||||
afterModel() {
|
||||
return this.get('settings').reload();
|
||||
},
|
||||
|
||||
actions: {
|
||||
save() {
|
||||
this.get('controller').send('save');
|
||||
},
|
||||
|
||||
willTransition(transition) {
|
||||
let controller = this.get('controller');
|
||||
let settings = this.get('settings');
|
||||
let modelIsDirty = settings.get('hasDirtyAttributes');
|
||||
|
||||
if (modelIsDirty) {
|
||||
transition.abort();
|
||||
controller.send('toggleLeaveSettingsModal', transition);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -33,6 +33,17 @@ export default AuthenticatedRoute.extend(styleBody, {
|
|||
actions: {
|
||||
save() {
|
||||
this.get('controller').send('save');
|
||||
},
|
||||
|
||||
willTransition(transition) {
|
||||
let controller = this.get('controller');
|
||||
let modelIsDirty = controller.get('dirtyAttributes');
|
||||
|
||||
if (modelIsDirty) {
|
||||
transition.abort();
|
||||
controller.send('toggleLeaveSettingsModal', transition);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -23,6 +23,18 @@ export default AuthenticatedRoute.extend(styleBody, CurrentUserSettings, {
|
|||
actions: {
|
||||
save() {
|
||||
this.get('controller').send('save');
|
||||
},
|
||||
|
||||
willTransition(transition) {
|
||||
let controller = this.get('controller');
|
||||
let settings = this.get('settings');
|
||||
let modelIsDirty = settings.get('hasDirtyAttributes');
|
||||
|
||||
if (modelIsDirty) {
|
||||
transition.abort();
|
||||
controller.send('toggleLeaveSettingsModal', transition);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -25,6 +25,8 @@ export default AuthenticatedRoute.extend(styleBody, CurrentUserSettings, {
|
|||
},
|
||||
|
||||
setupController(controller, models) {
|
||||
// reset the leave setting transition
|
||||
controller.set('leaveSettingsTransition', null);
|
||||
controller.set('model', models.settings);
|
||||
controller.set('themes', this.get('store').peekAll('theme'));
|
||||
this.get('controller').send('reset');
|
||||
|
@ -39,11 +41,15 @@ export default AuthenticatedRoute.extend(styleBody, CurrentUserSettings, {
|
|||
this.get('controller').send('save');
|
||||
},
|
||||
|
||||
willTransition() {
|
||||
// reset the model so that our CPs re-calc and unsaved changes aren't
|
||||
// persisted across transitions
|
||||
this.set('controller.model', null);
|
||||
return this._super(...arguments);
|
||||
willTransition(transition) {
|
||||
let controller = this.get('controller');
|
||||
let modelIsDirty = controller.get('dirtyAttributes');
|
||||
|
||||
if (modelIsDirty) {
|
||||
transition.abort();
|
||||
controller.send('toggleLeaveSettingsModal', transition);
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
activateTheme(theme) {
|
||||
|
|
|
@ -32,19 +32,6 @@ export default AuthenticatedRoute.extend(styleBody, CurrentUserSettings, {
|
|||
});
|
||||
},
|
||||
|
||||
deactivate() {
|
||||
let model = this.modelFor('team.user');
|
||||
|
||||
// we want to revert any unsaved changes on exit
|
||||
if (model && model.get('hasDirtyAttributes')) {
|
||||
model.rollbackAttributes();
|
||||
}
|
||||
|
||||
model.get('errors').clear();
|
||||
|
||||
this._super(...arguments);
|
||||
},
|
||||
|
||||
actions: {
|
||||
didTransition() {
|
||||
this.modelFor('team.user').get('errors').clear();
|
||||
|
@ -52,6 +39,19 @@ export default AuthenticatedRoute.extend(styleBody, CurrentUserSettings, {
|
|||
|
||||
save() {
|
||||
this.get('controller.save').perform();
|
||||
},
|
||||
|
||||
willTransition(transition) {
|
||||
let controller = this.get('controller');
|
||||
let user = controller.get('user');
|
||||
let dirtyAttributes = controller.get('dirtyAttributes');
|
||||
let modelIsDirty = user.get('hasDirtyAttributes');
|
||||
|
||||
if (modelIsDirty || dirtyAttributes) {
|
||||
transition.abort();
|
||||
controller.send('toggleLeaveSettingsModal', transition);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
<div class="gh-blognav-line">
|
||||
{{#gh-validation-status-container tagName="span" class="gh-blognav-label" errors=navItem.errors property="label" hasValidated=navItem.hasValidated}}
|
||||
{{gh-trim-focus-input navItem.label shouldFocus=navItem.last placeholder="Label" keyPress=(action "clearLabelErrors") update=(action (mut navItem.label))}}
|
||||
{{gh-trim-focus-input navItem.label shouldFocus=navItem.last placeholder="Label" keyPress=(action "clearLabelErrors") focusOut=(action "updateLabel" navItem.label) update=(action (mut navItem.label))}}
|
||||
{{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}}
|
||||
|
|
|
@ -10,6 +10,13 @@
|
|||
</section>
|
||||
</header>
|
||||
|
||||
{{#if showLeaveSettingsModal}}
|
||||
{{gh-fullscreen-modal "leave-settings"
|
||||
confirm=(action "leaveSettings")
|
||||
close=(action "toggleLeaveSettingsModal")
|
||||
modifier="action wide"}}
|
||||
{{/if}}
|
||||
|
||||
<section class="view-container">
|
||||
<br>
|
||||
<section class="app-grid">
|
||||
|
|
|
@ -10,6 +10,13 @@
|
|||
</section>
|
||||
</header>
|
||||
|
||||
{{#if showLeaveSettingsModal}}
|
||||
{{gh-fullscreen-modal "leave-settings"
|
||||
confirm=(action "leaveSettings")
|
||||
close=(action "toggleLeaveSettingsModal")
|
||||
modifier="action wide"}}
|
||||
{{/if}}
|
||||
|
||||
<section class="view-container">
|
||||
<br>
|
||||
<section class="app-grid">
|
||||
|
@ -30,7 +37,7 @@
|
|||
<div class="gh-setting-desc">Automatically send newly published posts to a channel in Slack</div>
|
||||
<div class="gh-setting-content-extended">
|
||||
{{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="url"}}
|
||||
{{gh-input model.url name="slack[url]" update=(action "updateURL") onenter=(action "save") placeholder="https://hooks.slack.com/services/..." data-test-slack-url-input=true}}
|
||||
{{gh-input model.url name="slack[url]" update=(action "updateURL") onenter=(action "save") focusOut=(action "triggerDirtyState") placeholder="https://hooks.slack.com/services/..." data-test-slack-url-input=true}}
|
||||
{{#unless model.errors.url}}
|
||||
<p>Set up a new incoming webhook <a href="https://my.slack.com/apps/new/A0F7XDUAZ-incoming-webhooks" target="_blank">here</a>, and grab the URL.</p>
|
||||
{{else}}
|
||||
|
|
|
@ -10,6 +10,13 @@
|
|||
</section>
|
||||
</header>
|
||||
|
||||
{{#if showLeaveSettingsModal}}
|
||||
{{gh-fullscreen-modal "leave-settings"
|
||||
confirm=(action "leaveSettings")
|
||||
close=(action "toggleLeaveSettingsModal")
|
||||
modifier="action wide"}}
|
||||
{{/if}}
|
||||
|
||||
<section class="view-container">
|
||||
<br>
|
||||
<section class="app-grid">
|
||||
|
|
|
@ -6,6 +6,13 @@
|
|||
</section>
|
||||
</header>
|
||||
|
||||
{{#if showLeaveSettingsModal}}
|
||||
{{gh-fullscreen-modal "leave-settings"
|
||||
confirm=(action "leaveSettings")
|
||||
close=(action "toggleLeaveSettingsModal")
|
||||
modifier="action wide"}}
|
||||
{{/if}}
|
||||
|
||||
<section class="view-continer">
|
||||
<form id="settings-code" novalidate="novalidate">
|
||||
<fieldset>
|
||||
|
|
|
@ -6,6 +6,13 @@
|
|||
</section>
|
||||
</header>
|
||||
|
||||
{{#if showLeaveSettingsModal}}
|
||||
{{gh-fullscreen-modal "leave-settings"
|
||||
confirm=(action "leaveSettings")
|
||||
close=(action "toggleLeaveSettingsModal")
|
||||
modifier="action wide"}}
|
||||
{{/if}}
|
||||
|
||||
<section class="view-container">
|
||||
<div class="gh-setting-header">Navigation</div>
|
||||
<div class="gh-blognav-container">
|
||||
|
@ -13,7 +20,7 @@
|
|||
{{#sortable-objects sortableObjectList=model.navigation useSwap=false}}
|
||||
{{#each model.navigation as |navItem|}}
|
||||
{{#draggable-object content=navItem dragHandle=".gh-blognav-grab" isSortable=true}}
|
||||
{{gh-navitem navItem=navItem baseUrl=blogUrl addItem="addNavItem" deleteItem="deleteNavItem" updateUrl="updateUrl"}}
|
||||
{{gh-navitem navItem=navItem baseUrl=blogUrl addItem="addNavItem" deleteItem="deleteNavItem" updateUrl="updateUrl" updateLabel="updateLabel"}}
|
||||
{{/draggable-object}}
|
||||
{{/each}}
|
||||
{{/sortable-objects}}
|
||||
|
|
|
@ -10,6 +10,13 @@
|
|||
{{/if}}
|
||||
</h2>
|
||||
|
||||
{{#if showLeaveSettingsModal}}
|
||||
{{gh-fullscreen-modal "leave-settings"
|
||||
confirm=(action "leaveSettings")
|
||||
close=(action "toggleLeaveSettingsModal")
|
||||
modifier="action wide"}}
|
||||
{{/if}}
|
||||
|
||||
<section class="view-actions">
|
||||
{{#if userActionsAreVisible}}
|
||||
<span class="dropdown">
|
||||
|
@ -118,7 +125,7 @@
|
|||
|
||||
{{#gh-form-group errors=user.errors hasValidated=user.hasValidated property="name" class="first-form-group"}}
|
||||
<label for="user-name">Full Name</label>
|
||||
{{gh-input user.name id="user-name" class="user-name" placeholder="Full Name" autocorrect="off" focusOut=(action "validate" "name" target=user) update=(action (mut user.name))}}
|
||||
{{gh-input user.name id="user-name" class="user-name" placeholder="Full Name" autocorrect="off" focusOut=(action "validate" "name" target=user) update=(action (mut user.name)) data-test-name-input=true}}
|
||||
{{#if user.errors.name}}
|
||||
{{gh-error-message errors=user.errors property="name"}}
|
||||
{{else}}
|
||||
|
@ -128,7 +135,7 @@
|
|||
|
||||
{{#gh-form-group errors=user.errors hasValidated=user.hasValidated property="slug"}}
|
||||
<label for="user-slug">Slug</label>
|
||||
{{gh-input slugValue class="user-name" id="user-slug" name="user" focusOut=(action (perform updateSlug slugValue)) placeholder="Slug" selectOnClick="true" autocorrect="off" update=(action (mut slugValue))}}
|
||||
{{gh-input slugValue class="user-name" id="user-slug" name="user" focusOut=(action (perform updateSlug slugValue)) placeholder="Slug" selectOnClick="true" autocorrect="off" update=(action (mut slugValue)) data-test-slug-input=true}}
|
||||
<p>{{gh-blog-url}}/author/{{slugValue}}</p>
|
||||
{{gh-error-message errors=user.errors property="slug"}}
|
||||
{{/gh-form-group}}
|
||||
|
@ -137,7 +144,7 @@
|
|||
<label for="user-email">Email</label>
|
||||
{{!-- Administrators only see text of Owner's email address but not input --}}
|
||||
{{#if canChangeEmail}}
|
||||
{{gh-input user.email type="email" id="user-email" name="email" placeholder="Email Address" autocapitalize="off" autocorrect="off" autocomplete="off" focusOut=(action "validate" "email" target=user) update=(action (mut user.email))}}
|
||||
{{gh-input user.email type="email" id="user-email" name="email" placeholder="Email Address" autocapitalize="off" autocorrect="off" autocomplete="off" focusOut=(action "validate" "email" target=user) update=(action (mut user.email)) data-test-email-input=true}}
|
||||
{{gh-error-message errors=user.errors property="email"}}
|
||||
{{else}}
|
||||
<span>{{user.email}}</span>
|
||||
|
@ -154,7 +161,7 @@
|
|||
options=roles
|
||||
optionValuePath="id"
|
||||
optionLabelPath="name"
|
||||
value=model.role
|
||||
value=user.role
|
||||
update=(action "changeRole")
|
||||
}}
|
||||
</span>
|
||||
|
@ -164,35 +171,35 @@
|
|||
|
||||
{{#gh-form-group errors=user.errors hasValidated=user.hasValidated property="location"}}
|
||||
<label for="user-location">Location</label>
|
||||
{{gh-input user.location type="text" id="user-location" focusOut=(action "validate" "location" target=user) update=(action (mut user.location))}}
|
||||
{{gh-input user.location type="text" id="user-location" focusOut=(action "validate" "location" target=user) update=(action (mut user.location)) data-test-location-input=true}}
|
||||
{{gh-error-message errors=user.errors property="location"}}
|
||||
<p>Where in the world do you live?</p>
|
||||
{{/gh-form-group}}
|
||||
|
||||
{{#gh-form-group errors=user.errors hasValidated=user.hasValidated property="website"}}
|
||||
<label for="user-website">Website</label>
|
||||
{{gh-input user.website type="url" id="user-website" autocapitalize="off" autocorrect="off" autocomplete="off" focusOut=(action "validate" "website" target=user) update=(action (mut user.website))}}
|
||||
{{gh-input user.website type="url" id="user-website" autocapitalize="off" autocorrect="off" autocomplete="off" focusOut=(action "validate" "website" target=user) update=(action (mut user.website)) data-test-website-input=true}}
|
||||
{{gh-error-message errors=user.errors property="website"}}
|
||||
<p>Have a website or blog other than this one? Link it!</p>
|
||||
{{/gh-form-group}}
|
||||
|
||||
{{#gh-form-group errors=user.errors hasValidated=user.hasValidated property="facebook"}}
|
||||
<label for="user-facebook">Facebook Profile</label>
|
||||
<input value={{user.facebook}} oninput={{action (mut _scratchFacebook) value="target.value"}} {{action "validateFacebookUrl" on="focusOut"}} type="url" class="gh-input" id="user-facebook" name="user[facebook]" placeholder="https://www.facebook.com/username" autocorrect="off" />
|
||||
<input value={{user.facebook}} oninput={{action (mut _scratchFacebook) value="target.value"}} {{action "validateFacebookUrl" on="focusOut"}} type="url" class="gh-input" id="user-facebook" name="user[facebook]" placeholder="https://www.facebook.com/username" autocorrect="off" data-test-facebook-input=true/>
|
||||
{{gh-error-message errors=user.errors property="facebook"}}
|
||||
<p>URL of your personal Facebook Profile</p>
|
||||
{{/gh-form-group}}
|
||||
|
||||
{{#gh-form-group errors=user.errors hasValidated=user.hasValidated property="twitter"}}
|
||||
<label for="user-twitter">Twitter Profile</label>
|
||||
<input value={{user.twitter}} oninput={{action (mut _scratchTwitter) value="target.value"}} {{action "validateTwitterUrl" on="focusOut"}} type="url" class="gh-input" id="user-twitter" name="user[twitter]" placeholder="https://twitter.com/username" autocorrect="off" />
|
||||
<input value={{user.twitter}} oninput={{action (mut _scratchTwitter) value="target.value"}} {{action "validateTwitterUrl" on="focusOut"}} type="url" class="gh-input" id="user-twitter" name="user[twitter]" placeholder="https://twitter.com/username" autocorrect="off" data-test-twitter-input=true/>
|
||||
{{gh-error-message errors=user.errors property="twitter"}}
|
||||
<p>URL of your personal Twitter profile</p>
|
||||
{{/gh-form-group}}
|
||||
|
||||
{{#gh-form-group errors=user.errors hasValidated=user.hasValidated property="bio" class="bio-container"}}
|
||||
<label for="user-bio">Bio</label>
|
||||
{{gh-textarea user.bio id="user-bio" focusOut=(action "validate" "bio" target=user) update=(action (mut user.bio))}}
|
||||
{{gh-textarea user.bio id="user-bio" focusOut=(action "validate" "bio" target=user) update=(action (mut user.bio)) data-test-bio-input=true}}
|
||||
{{gh-error-message errors=user.errors property="bio"}}
|
||||
<p>
|
||||
Write about you, in 200 characters or less.
|
||||
|
@ -213,25 +220,25 @@
|
|||
{{#unless isNotOwnProfile}}
|
||||
{{#gh-form-group errors=user.errors hasValidated=user.hasValidated property="password"}}
|
||||
<label for="user-password-old">Old Password</label>
|
||||
{{gh-input value=user.password type="password" id="user-password-old" update=(action 'updatePassword') onenter=(action (perform user.saveNewPassword))}}
|
||||
{{gh-input value=user.password type="password" id="user-password-old" update=(action 'updatePassword') onenter=(action (perform user.saveNewPassword)) data-test-old-pass-input=true}}
|
||||
{{gh-error-message errors=user.errors property="password"}}
|
||||
{{/gh-form-group}}
|
||||
{{/unless}}
|
||||
|
||||
{{#gh-form-group errors=user.errors hasValidated=user.hasValidated property="newPassword"}}
|
||||
<label for="user-password-new">New Password</label>
|
||||
{{gh-input user.newPassword type="password" id="user-password-new" update=(action 'updateNewPassword') onenter=(action (perform user.saveNewPassword))}}
|
||||
{{gh-input user.newPassword type="password" id="user-password-new" update=(action 'updateNewPassword') onenter=(action (perform user.saveNewPassword)) data-test-new-pass-input=true}}
|
||||
{{gh-error-message errors=user.errors property="newPassword"}}
|
||||
{{/gh-form-group}}
|
||||
|
||||
{{#gh-form-group errors=user.errors hasValidated=user.hasValidated property="ne2Password"}}
|
||||
<label for="user-new-password-verification">Verify Password</label>
|
||||
{{gh-input user.ne2Password type="password" id="user-new-password-verification" update=(action 'updateNe2Password') onenter=(action (perform user.saveNewPassword))}}
|
||||
{{gh-input user.ne2Password type="password" id="user-new-password-verification" update=(action 'updateNe2Password') onenter=(action (perform user.saveNewPassword)) data-test-ne2-pass-input=true}}
|
||||
{{gh-error-message errors=user.errors property="ne2Password"}}
|
||||
{{/gh-form-group}}
|
||||
|
||||
<div class="form-group">
|
||||
{{gh-task-button "Change Password" class="gh-btn gh-btn-red gh-btn-icon button-change-password" task=user.saveNewPassword}}
|
||||
{{gh-task-button "Change Password" class="gh-btn gh-btn-red gh-btn-icon button-change-password" task=user.saveNewPassword data-test-save-pw-button=true}}
|
||||
</div>
|
||||
</fieldset>
|
||||
</form> {{! change password form }}
|
||||
|
|
|
@ -8,7 +8,7 @@ Route.reopen({
|
|||
this.get('upgradeStatus').requireUpgrade();
|
||||
return false;
|
||||
} else {
|
||||
this._super(...arguments);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,5 +93,35 @@ describe('Acceptance: Settings - Apps - AMP', function () {
|
|||
expect(find('[data-test-amp-checkbox]').prop('checked'), 'AMP checkbox').to.be.true;
|
||||
expect(params.settings.findBy('key', 'amp').value).to.equal(true);
|
||||
});
|
||||
|
||||
it('warns when leaving without saving', async function () {
|
||||
await visit('/settings/apps/amp');
|
||||
|
||||
// has correct url
|
||||
expect(currentURL(), 'currentURL').to.equal('/settings/apps/amp');
|
||||
|
||||
// AMP is enabled by default
|
||||
expect(find('[data-test-amp-checkbox]').prop('checked'), 'AMP checkbox').to.be.true;
|
||||
|
||||
await click('[data-test-amp-checkbox]');
|
||||
|
||||
expect(find('[data-test-amp-checkbox]').prop('checked'), 'AMP checkbox').to.be.false;
|
||||
|
||||
await visit('/team');
|
||||
|
||||
expect(find('.fullscreen-modal').length, 'modal exists').to.equal(1);
|
||||
|
||||
// Leave without saving
|
||||
await(click('.fullscreen-modal [data-test-leave-button]'), 'leave without saving');
|
||||
|
||||
expect(currentURL(), 'currentURL').to.equal('/team');
|
||||
|
||||
await visit('/settings/apps/amp');
|
||||
|
||||
expect(currentURL(), 'currentURL').to.equal('/settings/apps/amp');
|
||||
|
||||
// settings were not saved
|
||||
expect(find('[data-test-amp-checkbox]').prop('checked'), 'AMP checkbox').to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -106,14 +106,24 @@ describe('Acceptance: Settings - Design', function () {
|
|||
).to.equal(1);
|
||||
});
|
||||
|
||||
it('clears unsaved settings when navigating away', async function () {
|
||||
it('clears unsaved settings when navigating away but warns with a confirmation dialog', async function () {
|
||||
await visit('/settings/design');
|
||||
await fillIn('.gh-blognav-label:first input', 'Test');
|
||||
await triggerEvent('.gh-blognav-label:first input', 'blur');
|
||||
|
||||
expect(find('.gh-blognav-label:first input').val()).to.equal('Test');
|
||||
// this.timeout(0);
|
||||
// return pauseTest();
|
||||
|
||||
await visit('/settings/code-injection');
|
||||
|
||||
expect(find('.fullscreen-modal').length, 'modal exists').to.equal(1);
|
||||
|
||||
// Leave without saving
|
||||
await(click('.fullscreen-modal [data-test-leave-button]'), 'leave without saving');
|
||||
|
||||
expect(currentURL(), 'currentURL').to.equal('/settings/code-injection');
|
||||
|
||||
await visit('/settings/design');
|
||||
|
||||
expect(find('.gh-blognav-label:first input').val()).to.equal('Home');
|
||||
|
|
|
@ -59,7 +59,7 @@ describe('Acceptance: Settings - Apps - Slack', function () {
|
|||
// has correct url
|
||||
expect(currentURL(), 'currentURL').to.equal('/settings/apps/slack');
|
||||
|
||||
await fillIn('#slack-settings input[name="slack[url]"]', 'notacorrecturl');
|
||||
await fillIn('[data-test-slack-url-input]', 'notacorrecturl');
|
||||
await click('[data-test-save-button]');
|
||||
|
||||
expect(find('#slack-settings .error .response').text().trim(), 'inline validation response')
|
||||
|
@ -81,7 +81,7 @@ describe('Acceptance: Settings - Apps - Slack', function () {
|
|||
expect(find('#slack-settings .error .response').text().trim(), 'inline validation response')
|
||||
.to.equal('');
|
||||
|
||||
await fillIn('#slack-settings input[name="slack[url]"]', 'https://hooks.slack.com/services/1275958430');
|
||||
await fillIn('[data-test-slack-url-input]', 'https://hooks.slack.com/services/1275958430');
|
||||
await click('[data-test-send-notification-button]');
|
||||
|
||||
expect(find('.gh-notification').length, 'number of notifications').to.equal(1);
|
||||
|
@ -107,5 +107,34 @@ describe('Acceptance: Settings - Apps - Slack', function () {
|
|||
expect(lastRequest.url).to.not.match(/\/slack\/test/);
|
||||
expect(find('.gh-notification').length, 'check slack notification after api validation error').to.equal(0);
|
||||
});
|
||||
|
||||
it('warns when leaving without saving', async function () {
|
||||
await visit('/settings/apps/slack');
|
||||
|
||||
// has correct url
|
||||
expect(currentURL(), 'currentURL').to.equal('/settings/apps/slack');
|
||||
|
||||
await fillIn('[data-test-slack-url-input]', 'https://hooks.slack.com/services/1275958430');
|
||||
await triggerEvent('[data-test-slack-url-input]', 'blur');
|
||||
|
||||
await visit('/settings/design');
|
||||
|
||||
expect(find('.fullscreen-modal').length, 'modal exists').to.equal(1);
|
||||
|
||||
// Leave without saving
|
||||
await(click('.fullscreen-modal [data-test-leave-button]'), 'leave without saving');
|
||||
|
||||
expect(currentURL(), 'currentURL').to.equal('/settings/design');
|
||||
|
||||
await visit('/settings/apps/slack');
|
||||
|
||||
expect(currentURL(), 'currentURL').to.equal('/settings/apps/slack');
|
||||
|
||||
// settings were not saved
|
||||
expect(
|
||||
find('[data-test-slack-url-input]').text().trim(),
|
||||
'Slack Webhook URL'
|
||||
).to.equal('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -92,5 +92,37 @@ describe('Acceptance: Settings - Apps - Unsplash', function () {
|
|||
[setting] = server.db.settings.where({key: 'unsplash'});
|
||||
expect(setting.value).to.equal('{"isActive":false}');
|
||||
});
|
||||
|
||||
it('warns when leaving without saving', async function () {
|
||||
await visit('/settings/apps/unsplash');
|
||||
|
||||
// has correct url
|
||||
expect(currentURL(), 'currentURL').to.equal('/settings/apps/unsplash');
|
||||
|
||||
expect(
|
||||
find('[data-test-checkbox="unsplash"]').prop('checked'),
|
||||
'checked by default'
|
||||
).to.be.true;
|
||||
|
||||
await click('[data-test-checkbox="unsplash"]');
|
||||
|
||||
expect(find('[data-test-checkbox="unsplash"]').prop('checked'), 'Unsplash checkbox').to.be.false;
|
||||
|
||||
await visit('/settings/labs');
|
||||
|
||||
expect(find('.fullscreen-modal').length, 'modal exists').to.equal(1);
|
||||
|
||||
// Leave without saving
|
||||
await(click('.fullscreen-modal [data-test-leave-button]'), 'leave without saving');
|
||||
|
||||
expect(currentURL(), 'currentURL').to.equal('/settings/labs');
|
||||
|
||||
await visit('/settings/apps/unsplash');
|
||||
|
||||
expect(currentURL(), 'currentURL').to.equal('/settings/apps/unsplash');
|
||||
|
||||
// settings were not saved
|
||||
expect(find('[data-test-checkbox="unsplash"]').prop('checked'), 'Unsplash checkbox').to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -465,31 +465,31 @@ describe('Acceptance: Team', function () {
|
|||
await visit('/team/test-1');
|
||||
|
||||
expect(currentURL(), 'currentURL').to.equal('/team/test-1');
|
||||
expect(find('.user-details-bottom .first-form-group input.user-name').val(), 'current user name').to.equal('Test User');
|
||||
expect(find('[data-test-name-input]').val(), 'current user name').to.equal('Test User');
|
||||
|
||||
expect(find('[data-test-save-button]').text().trim(), 'save button text').to.equal('Save');
|
||||
|
||||
// test empty user name
|
||||
await fillIn('.user-details-bottom .first-form-group input.user-name', '');
|
||||
await triggerEvent('.user-details-bottom .first-form-group input.user-name', 'blur');
|
||||
await fillIn('[data-test-name-input]', '');
|
||||
await triggerEvent('[data-test-name-input]', 'blur');
|
||||
|
||||
expect(find('.user-details-bottom .first-form-group').hasClass('error'), 'username input is in error state with blank input').to.be.true;
|
||||
|
||||
// test too long user name
|
||||
await fillIn('.user-details-bottom .first-form-group input.user-name', new Array(160).join('a'));
|
||||
await triggerEvent('.user-details-bottom .first-form-group input.user-name', 'blur');
|
||||
await fillIn('[data-test-name-input]', new Array(160).join('a'));
|
||||
await triggerEvent('[data-test-name-input]', 'blur');
|
||||
|
||||
expect(find('.user-details-bottom .first-form-group').hasClass('error'), 'username input is in error state with too long input').to.be.true;
|
||||
|
||||
// reset name field
|
||||
await fillIn('.user-details-bottom .first-form-group input.user-name', 'Test User');
|
||||
await fillIn('[data-test-name-input]', 'Test User');
|
||||
|
||||
expect(find('.user-details-bottom input[name="user"]').val(), 'slug value is default').to.equal('test-1');
|
||||
expect(find('[data-test-slug-input]').val(), 'slug value is default').to.equal('test-1');
|
||||
|
||||
await fillIn('.user-details-bottom input[name="user"]', '');
|
||||
await triggerEvent('.user-details-bottom input[name="user"]', 'blur');
|
||||
await fillIn('[data-test-slug-input]', '');
|
||||
await triggerEvent('[data-test-slug-input]', 'blur');
|
||||
|
||||
expect(find('.user-details-bottom input[name="user"]').val(), 'slug value is reset to original upon empty string').to.equal('test-1');
|
||||
expect(find('[data-test-slug-input]').val(), 'slug value is reset to original upon empty string').to.equal('test-1');
|
||||
|
||||
// Save changes
|
||||
await click('[data-test-save-button]');
|
||||
|
@ -497,7 +497,7 @@ describe('Acceptance: Team', function () {
|
|||
expect(find('[data-test-save-button]').text().trim(), 'save button text').to.equal('Saved');
|
||||
|
||||
// CMD-S shortcut works
|
||||
await fillIn('.user-details-bottom input[name="user"]', 'Test User');
|
||||
await fillIn('[data-test-slug-input]', 'Test User');
|
||||
await triggerEvent('.gh-app', 'keydown', {
|
||||
keyCode: 83, // s
|
||||
metaKey: ctrlOrCmd === 'command',
|
||||
|
@ -511,166 +511,166 @@ describe('Acceptance: Team', function () {
|
|||
|
||||
expect(params.users[0].name).to.equal('Test User');
|
||||
|
||||
await fillIn('.user-details-bottom input[name="user"]', 'white space');
|
||||
await triggerEvent('.user-details-bottom input[name="user"]', 'blur');
|
||||
await fillIn('[data-test-slug-input]', 'white space');
|
||||
await triggerEvent('[data-test-slug-input]', 'blur');
|
||||
|
||||
expect(find('.user-details-bottom input[name="user"]').val(), 'slug value is correctly dasherized').to.equal('white-space');
|
||||
expect(find('[data-test-slug-input]').val(), 'slug value is correctly dasherized').to.equal('white-space');
|
||||
|
||||
await fillIn('.user-details-bottom input[name="email"]', 'thisisnotanemail');
|
||||
await triggerEvent('.user-details-bottom input[name="email"]', 'blur');
|
||||
await fillIn('[data-test-email-input]', 'thisisnotanemail');
|
||||
await triggerEvent('[data-test-email-input]', 'blur');
|
||||
|
||||
expect(find('.user-details-bottom .form-group:nth-of-type(3)').hasClass('error'), 'email input should be in error state with invalid email').to.be.true;
|
||||
|
||||
await fillIn('.user-details-bottom input[name="email"]', 'test@example.com');
|
||||
await fillIn('#user-location', new Array(160).join('a'));
|
||||
await triggerEvent('#user-location', 'blur');
|
||||
await fillIn('[data-test-email-input]', 'test@example.com');
|
||||
await fillIn('[data-test-location-input]', new Array(160).join('a'));
|
||||
await triggerEvent('[data-test-location-input]', 'blur');
|
||||
|
||||
expect(find('#user-location').closest('.form-group').hasClass('error'), 'location input should be in error state').to.be.true;
|
||||
expect(find('[data-test-location-input]').closest('.form-group').hasClass('error'), 'location input should be in error state').to.be.true;
|
||||
|
||||
await fillIn('#user-location', '');
|
||||
await fillIn('#user-website', 'thisisntawebsite');
|
||||
await triggerEvent('#user-website', 'blur');
|
||||
await fillIn('[data-test-location-input]', '');
|
||||
await fillIn('[data-test-website-input]', 'thisisntawebsite');
|
||||
await triggerEvent('[data-test-website-input]', 'blur');
|
||||
|
||||
expect(find('#user-website').closest('.form-group').hasClass('error'), 'website input should be in error state').to.be.true;
|
||||
expect(find('[data-test-website-input]').closest('.form-group').hasClass('error'), 'website input should be in error state').to.be.true;
|
||||
|
||||
// Testing Facebook input
|
||||
|
||||
// displays initial value
|
||||
expect(find('#user-facebook').val(), 'initial facebook value')
|
||||
expect(find('[data-test-facebook-input]').val(), 'initial facebook value')
|
||||
.to.equal('https://www.facebook.com/test');
|
||||
|
||||
await triggerEvent('#user-facebook', 'focus');
|
||||
await triggerEvent('#user-facebook', 'blur');
|
||||
await triggerEvent('[data-test-facebook-input]', 'focus');
|
||||
await triggerEvent('[data-test-facebook-input]', 'blur');
|
||||
|
||||
// regression test: we still have a value after the input is
|
||||
// focused and then blurred without any changes
|
||||
expect(find('#user-facebook').val(), 'facebook value after blur with no change')
|
||||
expect(find('[data-test-facebook-input]').val(), 'facebook value after blur with no change')
|
||||
.to.equal('https://www.facebook.com/test');
|
||||
|
||||
await fillIn('#user-facebook', '');
|
||||
await fillIn('#user-facebook', ')(*&%^%)');
|
||||
await triggerEvent('#user-facebook', 'blur');
|
||||
await fillIn('[data-test-facebook-input]', '');
|
||||
await fillIn('[data-test-facebook-input]', ')(*&%^%)');
|
||||
await triggerEvent('[data-test-facebook-input]', 'blur');
|
||||
|
||||
expect(find('#user-facebook').closest('.form-group').hasClass('error'), 'facebook input should be in error state').to.be.true;
|
||||
expect(find('[data-test-facebook-input]').closest('.form-group').hasClass('error'), 'facebook input should be in error state').to.be.true;
|
||||
|
||||
await fillIn('#user-facebook', '');
|
||||
await fillIn('#user-facebook', 'pages/)(*&%^%)');
|
||||
await triggerEvent('#user-facebook', 'blur');
|
||||
await fillIn('[data-test-facebook-input]', '');
|
||||
await fillIn('[data-test-facebook-input]', 'pages/)(*&%^%)');
|
||||
await triggerEvent('[data-test-facebook-input]', 'blur');
|
||||
|
||||
expect(find('#user-facebook').val()).to.be.equal('https://www.facebook.com/pages/)(*&%^%)');
|
||||
expect(find('#user-facebook').closest('.form-group').hasClass('error'), 'facebook input should be in error state').to.be.false;
|
||||
expect(find('[data-test-facebook-input]').val()).to.be.equal('https://www.facebook.com/pages/)(*&%^%)');
|
||||
expect(find('[data-test-facebook-input]').closest('.form-group').hasClass('error'), 'facebook input should be in error state').to.be.false;
|
||||
|
||||
await fillIn('#user-facebook', '');
|
||||
await fillIn('#user-facebook', 'testing');
|
||||
await triggerEvent('#user-facebook', 'blur');
|
||||
await fillIn('[data-test-facebook-input]', '');
|
||||
await fillIn('[data-test-facebook-input]', 'testing');
|
||||
await triggerEvent('[data-test-facebook-input]', 'blur');
|
||||
|
||||
expect(find('#user-facebook').val()).to.be.equal('https://www.facebook.com/testing');
|
||||
expect(find('#user-facebook').closest('.form-group').hasClass('error'), 'facebook input should be in error state').to.be.false;
|
||||
expect(find('[data-test-facebook-input]').val()).to.be.equal('https://www.facebook.com/testing');
|
||||
expect(find('[data-test-facebook-input]').closest('.form-group').hasClass('error'), 'facebook input should be in error state').to.be.false;
|
||||
|
||||
await fillIn('#user-facebook', '');
|
||||
await fillIn('#user-facebook', 'somewebsite.com/pages/some-facebook-page/857469375913?ref=ts');
|
||||
await triggerEvent('#user-facebook', 'blur');
|
||||
await fillIn('[data-test-facebook-input]', '');
|
||||
await fillIn('[data-test-facebook-input]', 'somewebsite.com/pages/some-facebook-page/857469375913?ref=ts');
|
||||
await triggerEvent('[data-test-facebook-input]', 'blur');
|
||||
|
||||
expect(find('#user-facebook').val()).to.be.equal('https://www.facebook.com/pages/some-facebook-page/857469375913?ref=ts');
|
||||
expect(find('#user-facebook').closest('.form-group').hasClass('error'), 'facebook input should be in error state').to.be.false;
|
||||
expect(find('[data-test-facebook-input]').val()).to.be.equal('https://www.facebook.com/pages/some-facebook-page/857469375913?ref=ts');
|
||||
expect(find('[data-test-facebook-input]').closest('.form-group').hasClass('error'), 'facebook input should be in error state').to.be.false;
|
||||
|
||||
await fillIn('#user-facebook', '');
|
||||
await fillIn('#user-facebook', 'test');
|
||||
await triggerEvent('#user-facebook', 'blur');
|
||||
await fillIn('[data-test-facebook-input]', '');
|
||||
await fillIn('[data-test-facebook-input]', 'test');
|
||||
await triggerEvent('[data-test-facebook-input]', 'blur');
|
||||
|
||||
expect(find('#user-facebook').val()).to.be.equal('https://www.facebook.com/test');
|
||||
expect(find('#user-facebook').closest('.form-group').hasClass('error'), 'facebook input should be in error state').to.be.false;
|
||||
expect(find('[data-test-facebook-input]').val()).to.be.equal('https://www.facebook.com/test');
|
||||
expect(find('[data-test-facebook-input]').closest('.form-group').hasClass('error'), 'facebook input should be in error state').to.be.false;
|
||||
|
||||
await fillIn('#user-facebook', '');
|
||||
await fillIn('#user-facebook', 'http://twitter.com/testuser');
|
||||
await triggerEvent('#user-facebook', 'blur');
|
||||
await fillIn('[data-test-facebook-input]', '');
|
||||
await fillIn('[data-test-facebook-input]', 'http://twitter.com/testuser');
|
||||
await triggerEvent('[data-test-facebook-input]', 'blur');
|
||||
|
||||
expect(find('#user-facebook').val()).to.be.equal('https://www.facebook.com/testuser');
|
||||
expect(find('#user-facebook').closest('.form-group').hasClass('error'), 'facebook input should be in error state').to.be.false;
|
||||
expect(find('[data-test-facebook-input]').val()).to.be.equal('https://www.facebook.com/testuser');
|
||||
expect(find('[data-test-facebook-input]').closest('.form-group').hasClass('error'), 'facebook input should be in error state').to.be.false;
|
||||
|
||||
await fillIn('#user-facebook', '');
|
||||
await fillIn('#user-facebook', 'facebook.com/testing');
|
||||
await triggerEvent('#user-facebook', 'blur');
|
||||
await fillIn('[data-test-facebook-input]', '');
|
||||
await fillIn('[data-test-facebook-input]', 'facebook.com/testing');
|
||||
await triggerEvent('[data-test-facebook-input]', 'blur');
|
||||
|
||||
expect(find('#user-facebook').val()).to.be.equal('https://www.facebook.com/testing');
|
||||
expect(find('#user-facebook').closest('.form-group').hasClass('error'), 'facebook input should be in error state').to.be.false;
|
||||
expect(find('[data-test-facebook-input]').val()).to.be.equal('https://www.facebook.com/testing');
|
||||
expect(find('[data-test-facebook-input]').closest('.form-group').hasClass('error'), 'facebook input should be in error state').to.be.false;
|
||||
|
||||
// Testing Twitter input
|
||||
|
||||
// loads fixtures and performs transform
|
||||
expect(find('#user-twitter').val(), 'initial twitter value')
|
||||
expect(find('[data-test-twitter-input]').val(), 'initial twitter value')
|
||||
.to.equal('https://twitter.com/test');
|
||||
|
||||
await triggerEvent('#user-twitter', 'focus');
|
||||
await triggerEvent('#user-twitter', 'blur');
|
||||
await triggerEvent('[data-test-twitter-input]', 'focus');
|
||||
await triggerEvent('[data-test-twitter-input]', 'blur');
|
||||
|
||||
// regression test: we still have a value after the input is
|
||||
// focused and then blurred without any changes
|
||||
expect(find('#user-twitter').val(), 'twitter value after blur with no change')
|
||||
expect(find('[data-test-twitter-input]').val(), 'twitter value after blur with no change')
|
||||
.to.equal('https://twitter.com/test');
|
||||
|
||||
await fillIn('#user-twitter', '');
|
||||
await fillIn('#user-twitter', ')(*&%^%)');
|
||||
await triggerEvent('#user-twitter', 'blur');
|
||||
await fillIn('[data-test-twitter-input]', '');
|
||||
await fillIn('[data-test-twitter-input]', ')(*&%^%)');
|
||||
await triggerEvent('[data-test-twitter-input]', 'blur');
|
||||
|
||||
expect(find('#user-twitter').closest('.form-group').hasClass('error'), 'twitter input should be in error state').to.be.true;
|
||||
expect(find('[data-test-twitter-input]').closest('.form-group').hasClass('error'), 'twitter input should be in error state').to.be.true;
|
||||
|
||||
await fillIn('#user-twitter', '');
|
||||
await fillIn('#user-twitter', 'name');
|
||||
await triggerEvent('#user-twitter', 'blur');
|
||||
await fillIn('[data-test-twitter-input]', '');
|
||||
await fillIn('[data-test-twitter-input]', 'name');
|
||||
await triggerEvent('[data-test-twitter-input]', 'blur');
|
||||
|
||||
expect(find('#user-twitter').val()).to.be.equal('https://twitter.com/name');
|
||||
expect(find('#user-twitter').closest('.form-group').hasClass('error'), 'twitter input should be in error state').to.be.false;
|
||||
expect(find('[data-test-twitter-input]').val()).to.be.equal('https://twitter.com/name');
|
||||
expect(find('[data-test-twitter-input]').closest('.form-group').hasClass('error'), 'twitter input should be in error state').to.be.false;
|
||||
|
||||
await fillIn('#user-twitter', '');
|
||||
await fillIn('#user-twitter', 'http://github.com/user');
|
||||
await triggerEvent('#user-twitter', 'blur');
|
||||
await fillIn('[data-test-twitter-input]', '');
|
||||
await fillIn('[data-test-twitter-input]', 'http://github.com/user');
|
||||
await triggerEvent('[data-test-twitter-input]', 'blur');
|
||||
|
||||
expect(find('#user-twitter').val()).to.be.equal('https://twitter.com/user');
|
||||
expect(find('#user-twitter').closest('.form-group').hasClass('error'), 'twitter input should be in error state').to.be.false;
|
||||
expect(find('[data-test-twitter-input]').val()).to.be.equal('https://twitter.com/user');
|
||||
expect(find('[data-test-twitter-input]').closest('.form-group').hasClass('error'), 'twitter input should be in error state').to.be.false;
|
||||
|
||||
await fillIn('#user-twitter', '');
|
||||
await fillIn('#user-twitter', 'twitter.com/user');
|
||||
await triggerEvent('#user-twitter', 'blur');
|
||||
await fillIn('[data-test-twitter-input]', '');
|
||||
await fillIn('[data-test-twitter-input]', 'twitter.com/user');
|
||||
await triggerEvent('[data-test-twitter-input]', 'blur');
|
||||
|
||||
expect(find('#user-twitter').val()).to.be.equal('https://twitter.com/user');
|
||||
expect(find('#user-twitter').closest('.form-group').hasClass('error'), 'twitter input should be in error state').to.be.false;
|
||||
expect(find('[data-test-twitter-input]').val()).to.be.equal('https://twitter.com/user');
|
||||
expect(find('[data-test-twitter-input]').closest('.form-group').hasClass('error'), 'twitter input should be in error state').to.be.false;
|
||||
|
||||
await fillIn('#user-website', '');
|
||||
await fillIn('#user-bio', new Array(210).join('a'));
|
||||
await triggerEvent('#user-bio', 'blur');
|
||||
await fillIn('[data-test-website-input]', '');
|
||||
await fillIn('[data-test-bio-input]', new Array(210).join('a'));
|
||||
await triggerEvent('[data-test-bio-input]', 'blur');
|
||||
|
||||
expect(find('#user-bio').closest('.form-group').hasClass('error'), 'bio input should be in error state').to.be.true;
|
||||
expect(find('[data-test-bio-input]').closest('.form-group').hasClass('error'), 'bio input should be in error state').to.be.true;
|
||||
|
||||
// password reset ------
|
||||
|
||||
// button triggers validation
|
||||
await click('.button-change-password');
|
||||
await click('[data-test-save-pw-button]');
|
||||
|
||||
expect(
|
||||
find('#user-password-new').closest('.form-group').hasClass('error'),
|
||||
find('[data-test-new-pass-input]').closest('.form-group').hasClass('error'),
|
||||
'new password has error class when blank'
|
||||
).to.be.true;
|
||||
|
||||
expect(
|
||||
find('#user-password-new').siblings('.response').text(),
|
||||
find('[data-test-new-pass-input]').siblings('.response').text(),
|
||||
'new password error when blank'
|
||||
).to.match(/can't be blank/);
|
||||
|
||||
// validates too short password (< 10 characters)
|
||||
await fillIn('#user-password-new', 'notlong');
|
||||
await fillIn('#user-new-password-verification', 'notlong');
|
||||
await fillIn('[data-test-new-pass-input]', 'notlong');
|
||||
await fillIn('[data-test-ne2-pass-input]', 'notlong');
|
||||
|
||||
// enter key triggers action
|
||||
await keyEvent('#user-password-new', 'keyup', 13);
|
||||
await keyEvent('[data-test-new-pass-input]', 'keyup', 13);
|
||||
|
||||
expect(
|
||||
find('#user-password-new').closest('.form-group').hasClass('error'),
|
||||
find('[data-test-new-pass-input]').closest('.form-group').hasClass('error'),
|
||||
'new password has error class when password too short'
|
||||
).to.be.true;
|
||||
|
||||
expect(
|
||||
find('#user-password-new').siblings('.response').text(),
|
||||
find('[data-test-new-pass-input]').siblings('.response').text(),
|
||||
'confirm password error when it\'s too short'
|
||||
).to.match(/at least 10 characters long/);
|
||||
|
||||
|
@ -692,30 +692,30 @@ describe('Acceptance: Team', function () {
|
|||
).to.match(/you cannot use an insecure password/);
|
||||
|
||||
// typing in inputs clears validation
|
||||
await fillIn('#user-password-new', 'thisissupersafe');
|
||||
await triggerEvent('#user-password-new', 'input');
|
||||
await fillIn('[data-test-new-pass-input]', 'thisissupersafe');
|
||||
await triggerEvent('[data-test-new-pass-input]', 'input');
|
||||
|
||||
expect(
|
||||
find('#user-password-new').closest('.form-group').hasClass('error'),
|
||||
find('[data-test-new-pass-input]').closest('.form-group').hasClass('error'),
|
||||
'password validation is visible after typing'
|
||||
).to.be.false;
|
||||
|
||||
// enter key triggers action
|
||||
await keyEvent('#user-password-new', 'keyup', 13);
|
||||
await keyEvent('[data-test-new-pass-input]', 'keyup', 13);
|
||||
|
||||
expect(
|
||||
find('#user-new-password-verification').closest('.form-group').hasClass('error'),
|
||||
find('[data-test-ne2-pass-input]').closest('.form-group').hasClass('error'),
|
||||
'confirm password has error class when it doesn\'t match'
|
||||
).to.be.true;
|
||||
|
||||
expect(
|
||||
find('#user-new-password-verification').siblings('.response').text(),
|
||||
find('[data-test-ne2-pass-input]').siblings('.response').text(),
|
||||
'confirm password error when it doesn\'t match'
|
||||
).to.match(/do not match/);
|
||||
|
||||
// submits with correct details
|
||||
await fillIn('#user-new-password-verification', 'thisissupersafe');
|
||||
await click('.button-change-password');
|
||||
await fillIn('[data-test-ne2-pass-input]', 'thisissupersafe');
|
||||
await click('[data-test-save-pw-button]');
|
||||
|
||||
// hits the endpoint
|
||||
let [newRequest] = server.pretender.handledRequests.slice(-1);
|
||||
|
@ -731,12 +731,12 @@ describe('Acceptance: Team', function () {
|
|||
|
||||
// clears the fields
|
||||
expect(
|
||||
find('#user-password-new').val(),
|
||||
find('[data-test-new-pass-input]').val(),
|
||||
'password field after submit'
|
||||
).to.be.blank;
|
||||
|
||||
expect(
|
||||
find('#user-new-password-verification').val(),
|
||||
find('[data-test-ne2-pass-input]').val(),
|
||||
'password verification field after submit'
|
||||
).to.be.blank;
|
||||
|
||||
|
@ -746,6 +746,39 @@ describe('Acceptance: Team', function () {
|
|||
'password saved notification is displayed'
|
||||
).to.equal(1);
|
||||
});
|
||||
|
||||
it('warns when leaving without saving', async function () {
|
||||
await visit('/team/test-1');
|
||||
|
||||
expect(currentURL(), 'currentURL').to.equal('/team/test-1');
|
||||
|
||||
await fillIn('[data-test-slug-input]', 'another slug');
|
||||
await triggerEvent('[data-test-slug-input]', 'blur');
|
||||
|
||||
expect(find('[data-test-slug-input]').val()).to.be.equal('another-slug');
|
||||
|
||||
await fillIn('[data-test-facebook-input]', 'testuser');
|
||||
await triggerEvent('[data-test-facebook-input]', 'blur');
|
||||
|
||||
expect(find('[data-test-facebook-input]').val()).to.be.equal('https://www.facebook.com/testuser');
|
||||
|
||||
await visit('/settings/team');
|
||||
|
||||
expect(find('.fullscreen-modal').length, 'modal exists').to.equal(1);
|
||||
|
||||
// Leave without saving
|
||||
await(click('.fullscreen-modal [data-test-leave-button]'), 'leave without saving');
|
||||
|
||||
expect(currentURL(), 'currentURL').to.equal('/settings/team');
|
||||
|
||||
await visit('/team/test-1');
|
||||
|
||||
expect(currentURL(), 'currentURL').to.equal('/team/test-1');
|
||||
|
||||
// settings were not saved
|
||||
expect(find('[data-test-slug-input]').val()).to.be.equal('test-1');
|
||||
expect(find('[data-test-facebook-input]').val()).to.be.equal('https://www.facebook.com/test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('own user', function () {
|
||||
|
@ -753,36 +786,36 @@ describe('Acceptance: Team', function () {
|
|||
await visit(`/team/${admin.slug}`);
|
||||
|
||||
// test the "old password" field is validated
|
||||
await click('.button-change-password');
|
||||
await click('[data-test-save-pw-button]');
|
||||
|
||||
// old password has error
|
||||
expect(
|
||||
find('#user-password-old').closest('.form-group').hasClass('error'),
|
||||
find('[data-test-old-pass-input]').closest('.form-group').hasClass('error'),
|
||||
'old password has error class when blank'
|
||||
).to.be.true;
|
||||
|
||||
expect(
|
||||
find('#user-password-old').siblings('.response').text(),
|
||||
find('[data-test-old-pass-input]').siblings('.response').text(),
|
||||
'old password error when blank'
|
||||
).to.match(/is required/);
|
||||
|
||||
// new password has error
|
||||
expect(
|
||||
find('#user-password-new').closest('.form-group').hasClass('error'),
|
||||
find('[data-test-new-pass-input]').closest('.form-group').hasClass('error'),
|
||||
'new password has error class when blank'
|
||||
).to.be.true;
|
||||
|
||||
expect(
|
||||
find('#user-password-new').siblings('.response').text(),
|
||||
find('[data-test-new-pass-input]').siblings('.response').text(),
|
||||
'new password error when blank'
|
||||
).to.match(/can't be blank/);
|
||||
|
||||
// validation is cleared when typing
|
||||
await fillIn('#user-password-old', 'password');
|
||||
await triggerEvent('#user-password-old', 'input');
|
||||
await fillIn('[data-test-old-pass-input]', 'password');
|
||||
await triggerEvent('[data-test-old-pass-input]', 'input');
|
||||
|
||||
expect(
|
||||
find('#user-password-old').closest('.form-group').hasClass('error'),
|
||||
find('[data-test-old-pass-input]').closest('.form-group').hasClass('error'),
|
||||
'old password validation is in error state after typing'
|
||||
).to.be.false;
|
||||
});
|
||||
|
|
|
@ -80,7 +80,7 @@ describe('Integration: Component: gh-navitem', function () {
|
|||
expect(addActionCallCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('triggers update action', function () {
|
||||
it('triggers update url action', function () {
|
||||
this.set('navItem', NavItem.create({label: 'Test', url: '/url'}));
|
||||
|
||||
let updateActionCallCount = 0;
|
||||
|
@ -94,6 +94,20 @@ describe('Integration: Component: gh-navitem', function () {
|
|||
expect(updateActionCallCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('triggers update label action', function () {
|
||||
this.set('navItem', NavItem.create({label: 'Test', url: '/url'}));
|
||||
|
||||
let updateActionCallCount = 0;
|
||||
this.on('update', () => {
|
||||
updateActionCallCount++;
|
||||
});
|
||||
|
||||
this.render(hbs`{{gh-navitem navItem=navItem baseUrl=baseUrl updateLabel="update"}}`);
|
||||
this.$('.gh-blognav-label input').trigger('blur');
|
||||
|
||||
expect(updateActionCallCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('displays inline errors', function () {
|
||||
this.set('navItem', NavItem.create({label: '', url: ''}));
|
||||
this.get('navItem').validate();
|
||||
|
|
Loading…
Reference in New Issue