Minor tag settings validation updates & fixes

refs #5845
- add tests for `tag-settings` validator
- add validation for tag slug length
- fix display of error message when saving tag fails on the server
- add max chars text to description char count, remove error message as the count/input colour already indicates an error
This commit is contained in:
Kevin Ansfield 2015-10-14 18:34:03 +01:00
parent 917f1b32f5
commit 510bcd8826
4 changed files with 314 additions and 7 deletions

View File

@ -50,7 +50,7 @@ export default Ember.Controller.extend(SettingsMenuMixin, {
activeTag.save().catch(function (error) { activeTag.save().catch(function (error) {
if (error) { if (error) {
self.notifications.showAPIError(error, {key: 'tag.save'}); self.get('notifications').showAPIError(error, {key: 'tag.save'});
} }
}); });
}, },

View File

@ -16,17 +16,17 @@
{{gh-error-message errors=activeTag.errors property="name"}} {{gh-error-message errors=activeTag.errors property="name"}}
{{/gh-form-group}} {{/gh-form-group}}
<div class="form-group"> {{#gh-form-group errors=activeTag.errors hasValidated=activeTag.hasValidated property="slug"}}
<label for="tag-url">URL</label> <label for="tag-url">URL</label>
{{gh-input id="tag-url" name="url" type="text" value=activeTagSlugScratch focus-out="saveActiveTagSlug"}} {{gh-input id="tag-url" name="url" type="text" value=activeTagSlugScratch focus-out="saveActiveTagSlug"}}
{{gh-url-preview prefix="tag" slug=activeTagSlugScratch tagName="p" classNames="description"}} {{gh-url-preview prefix="tag" slug=activeTagSlugScratch tagName="p" classNames="description"}}
</div> {{gh-error-message errors=activeTag.errors property="slug"}}
{{/gh-form-group}}
{{#gh-form-group errors=activeTag.errors hasValidated=activeTag.hasValidated property="description"}} {{#gh-form-group errors=activeTag.errors hasValidated=activeTag.hasValidated property="description"}}
<label for="tag-description">Description</label> <label for="tag-description">Description</label>
{{gh-textarea id="tag-description" name="description" value=activeTagDescriptionScratch focus-out="saveActiveTagDescription"}} {{gh-textarea id="tag-description" name="description" value=activeTagDescriptionScratch focus-out="saveActiveTagDescription"}}
{{gh-error-message errors=activeTag.errors property="description"}} <p>Maximum: <b>200</b> characters. Youve used {{gh-count-down-characters activeTagDescriptionScratch 200}}</p>
{{gh-count-down-characters activeTagDescriptionScratch 200}}
{{/gh-form-group}} {{/gh-form-group}}
<ul class="nav-list nav-list-block"> <ul class="nav-list nav-list-block">

View File

@ -1,7 +1,8 @@
import BaseValidator from './base'; import BaseValidator from './base';
export default BaseValidator.create({ export default BaseValidator.create({
properties: ['name', 'description', 'metaTitle', 'metaDescription'], properties: ['name', 'slug', 'description', 'metaTitle', 'metaDescription'],
name: function (model) { name: function (model) {
var name = model.get('name'); var name = model.get('name');
@ -17,11 +18,20 @@ export default BaseValidator.create({
} }
}, },
slug: function (model) {
var slug = model.get('slug');
if (!validator.isLength(slug, 0, 150)) {
model.get('errors').add('slug', 'URL cannot be longer than 150 characters.');
this.invalidate();
}
},
description: function (model) { description: function (model) {
var description = model.get('description'); var description = model.get('description');
if (!validator.isLength(description, 0, 200)) { if (!validator.isLength(description, 0, 200)) {
model.get('errors').add('description', 'Description cannot be longer than 200 characters'); model.get('errors').add('description', 'Description cannot be longer than 200 characters.');
this.invalidate(); this.invalidate();
} }
}, },
@ -34,6 +44,7 @@ export default BaseValidator.create({
this.invalidate(); this.invalidate();
} }
}, },
metaDescription: function (model) { metaDescription: function (model) {
var metaDescription = model.get('meta_description'); var metaDescription = model.get('meta_description');

View File

@ -0,0 +1,296 @@
/* jshint expr:true */
import { expect } from 'chai';
import {
describe,
it
} from 'mocha';
import sinon from 'sinon';
import Ember from 'ember';
// import validator from 'ghost/validators/tag-settings';
import ValidationEngine from 'ghost/mixins/validation-engine';
const {run} = Ember,
Tag = Ember.Object.extend(ValidationEngine, {
validationType: 'tag',
name: null,
description: null,
meta_title: null,
meta_description: null
});
// TODO: These tests have way too much duplication, consider creating test
// helpers for validations
describe('Unit: Validator: tag-settings', function () {
it('validates all fields by default', function () {
let tag = Tag.create({}),
properties = tag.get('validators.tag.properties');
// TODO: This is checking implementation details rather than expected
// behaviour. Replace once we have consistent behaviour (see below)
expect(properties, 'properties').to.include('name');
expect(properties, 'properties').to.include('slug');
expect(properties, 'properties').to.include('description');
expect(properties, 'properties').to.include('metaTitle');
expect(properties, 'properties').to.include('metaDescription');
// TODO: .validate (and by extension .save) doesn't currently affect
// .hasValidated - it would be good to make this consistent.
// The following tests currently fail:
//
// run(() => {
// tag.validate();
// });
//
// expect(tag.get('hasValidated'), 'hasValidated').to.include('name');
// expect(tag.get('hasValidated'), 'hasValidated').to.include('description');
// expect(tag.get('hasValidated'), 'hasValidated').to.include('metaTitle');
// expect(tag.get('hasValidated'), 'hasValidated').to.include('metaDescription');
});
it('passes with valid name', function () {
// longest valid name
let tag = Tag.create({name: (new Array(151).join('x'))}),
passed = false;
expect(tag.get('name').length, 'name length').to.equal(150);
run(() => {
tag.validate({property: 'name'}).then(() => {
passed = true;
});
});
expect(passed, 'passed').to.be.true;
expect(tag.get('hasValidated'), 'hasValidated').to.include('name');
});
it('validates name presence', function () {
let tag = Tag.create(),
passed = false;
// TODO: validator is currently a singleton meaning state leaks
// between all objects that use it. Each object should either
// get it's own validator instance or validator objects should not
// contain state. The following currently fails:
//
// let validator = tag.get('validators.tag')
// expect(validator.get('passed'), 'passed').to.be.false;
run(() => {
tag.validate({property: 'name'}).then(() => {
passed = true;
});
});
let nameErrors = tag.get('errors').errorsFor('name')[0];
expect(nameErrors.attribute, 'errors.name.attribute').to.equal('name');
expect(nameErrors.message, 'errors.name.message').to.equal('You must specify a name for the tag.');
expect(passed, 'passed').to.be.false;
expect(tag.get('hasValidated'), 'hasValidated').to.include('name');
});
it('validates names starting with a comma', function () {
let tag = Tag.create({name: ',test'}),
passed = false;
run(() => {
tag.validate({property: 'name'}).then(() => {
passed = true;
});
});
let nameErrors = tag.get('errors').errorsFor('name')[0];
expect(nameErrors.attribute, 'errors.name.attribute').to.equal('name');
expect(nameErrors.message, 'errors.name.message').to.equal('Tag names can\'t start with commas.');
expect(tag.get('errors.length')).to.equal(1);
expect(passed, 'passed').to.be.false;
expect(tag.get('hasValidated'), 'hasValidated').to.include('name');
});
it('validates name length', function () {
// shortest invalid name
let tag = Tag.create({name: (new Array(152).join('x'))}),
passed = false;
expect(tag.get('name').length, 'name length').to.equal(151);
run(() => {
tag.validate({property: 'name'}).then(() => {
passed = true;
});
});
let nameErrors = tag.get('errors').errorsFor('name')[0];
expect(nameErrors.attribute, 'errors.name.attribute').to.equal('name');
expect(nameErrors.message, 'errors.name.message').to.equal('Tag names cannot be longer than 150 characters.');
expect(passed, 'passed').to.be.false;
expect(tag.get('hasValidated'), 'hasValidated').to.include('name');
});
it('passes with valid slug', function () {
// longest valid slug
let tag = Tag.create({slug: (new Array(151).join('x'))}),
passed = false;
expect(tag.get('slug').length, 'slug length').to.equal(150);
run(() => {
tag.validate({property: 'slug'}).then(() => {
passed = true;
});
});
expect(passed, 'passed').to.be.true;
expect(tag.get('hasValidated'), 'hasValidated').to.include('slug');
});
it('validates slug length', function () {
// shortest invalid slug
let tag = Tag.create({slug: (new Array(152).join('x'))}),
passed = false;
expect(tag.get('slug').length, 'slug length').to.equal(151);
run(() => {
tag.validate({property: 'slug'}).then(() => {
passed = true;
});
});
let slugErrors = tag.get('errors').errorsFor('slug')[0];
expect(slugErrors.attribute, 'errors.slug.attribute').to.equal('slug');
expect(slugErrors.message, 'errors.slug.message').to.equal('URL cannot be longer than 150 characters.');
expect(passed, 'passed').to.be.false;
expect(tag.get('hasValidated'), 'hasValidated').to.include('slug');
});
it('passes with a valid description', function () {
// longest valid description
let tag = Tag.create({description: (new Array(201).join('x'))}),
passed = false;
expect(tag.get('description').length, 'description length').to.equal(200);
run(() => {
tag.validate({property: 'description'}).then(() => {
passed = true;
});
});
expect(passed, 'passed').to.be.true;
expect(tag.get('hasValidated'), 'hasValidated').to.include('description');
});
it('validates description length', function () {
// shortest invalid description
let tag = Tag.create({description: (new Array(202).join('x'))}),
passed = false;
expect(tag.get('description').length, 'description length').to.equal(201);
run(() => {
tag.validate({property: 'description'}).then(() => {
passed = true;
});
});
let errors = tag.get('errors').errorsFor('description')[0];
expect(errors.attribute, 'errors.description.attribute').to.equal('description');
expect(errors.message, 'errors.description.message').to.equal('Description cannot be longer than 200 characters.');
// TODO: tag.errors appears to be a singleton and previous errors are
// not cleared despite creating a new tag object
//
// console.log(JSON.stringify(tag.get('errors')));
// expect(tag.get('errors.length')).to.equal(1);
expect(passed, 'passed').to.be.false;
expect(tag.get('hasValidated'), 'hasValidated').to.include('description');
});
// TODO: we have both meta_title and metaTitle property names on the
// model/validator respectively - this should be standardised
it('passes with a valid meta_title', function () {
// longest valid meta_title
let tag = Tag.create({meta_title: (new Array(151).join('x'))}),
passed = false;
expect(tag.get('meta_title').length, 'meta_title length').to.equal(150);
run(() => {
tag.validate({property: 'metaTitle'}).then(() => {
passed = true;
});
});
expect(passed, 'passed').to.be.true;
expect(tag.get('hasValidated'), 'hasValidated').to.include('metaTitle');
});
it('validates meta_title length', function () {
// shortest invalid meta_title
let tag = Tag.create({meta_title: (new Array(152).join('x'))}),
passed = false;
expect(tag.get('meta_title').length, 'meta_title length').to.equal(151);
run(() => {
tag.validate({property: 'metaTitle'}).then(() => {
passed = true;
});
});
let errors = tag.get('errors').errorsFor('meta_title')[0];
expect(errors.attribute, 'errors.meta_title.attribute').to.equal('meta_title');
expect(errors.message, 'errors.meta_title.message').to.equal('Meta Title cannot be longer than 150 characters.');
expect(passed, 'passed').to.be.false;
expect(tag.get('hasValidated'), 'hasValidated').to.include('metaTitle');
});
// TODO: we have both meta_description and metaDescription property names on
// the model/validator respectively - this should be standardised
it('passes with a valid meta_description', function () {
// longest valid description
let tag = Tag.create({meta_description: (new Array(201).join('x'))}),
passed = false;
expect(tag.get('meta_description').length, 'meta_description length').to.equal(200);
run(() => {
tag.validate({property: 'metaDescription'}).then(() => {
passed = true;
});
});
expect(passed, 'passed').to.be.true;
expect(tag.get('hasValidated'), 'hasValidated').to.include('metaDescription');
});
it('validates meta_description length', function () {
// shortest invalid meta_description
let tag = Tag.create({meta_description: (new Array(202).join('x'))}),
passed = false;
expect(tag.get('meta_description').length, 'meta_description length').to.equal(201);
run(() => {
tag.validate({property: 'metaDescription'}).then(() => {
passed = true;
});
});
let errors = tag.get('errors').errorsFor('meta_description')[0];
expect(errors.attribute, 'errors.meta_description.attribute').to.equal('meta_description');
expect(errors.message, 'errors.meta_description.message').to.equal('Meta Description cannot be longer than 200 characters.');
expect(passed, 'passed').to.be.false;
expect(tag.get('hasValidated'), 'hasValidated').to.include('metaDescription');
});
});