diff --git a/ghost/admin/app/components/gh-benefit-item.hbs b/ghost/admin/app/components/gh-benefit-item.hbs index 2406575f4c..21738c8a2c 100644 --- a/ghost/admin/app/components/gh-benefit-item.hbs +++ b/ghost/admin/app/components/gh-benefit-item.hbs @@ -1,39 +1,46 @@ -{{#unless this.benefitItem.isNew}} - - {{svg-jar "grab"}} - Reorder - -{{/unless}} +
+ {{#unless @benefitItem.isNew}} + + {{svg-jar "grab"}} + Reorder + + {{/unless}} -
- {{svg-jar "check-2"}} - - +
+ {{svg-jar "check-2"}} + + - - -
+ +
+
-{{#if this.benefitItem.isNew}} - -{{else}} - -{{/if}} + {{#if @benefitItem.isNew}} + + {{else}} + + {{/if}} + +
\ No newline at end of file diff --git a/ghost/admin/app/components/gh-benefit-item.js b/ghost/admin/app/components/gh-benefit-item.js index cce8042910..ec32702510 100644 --- a/ghost/admin/app/components/gh-benefit-item.js +++ b/ghost/admin/app/components/gh-benefit-item.js @@ -1,58 +1,19 @@ -import Component from '@ember/component'; -import ValidationState from 'ghost-admin/mixins/validation-state'; -import boundOneWay from 'ghost-admin/utils/bound-one-way'; -import {computed} from '@ember/object'; -import {readOnly} from '@ember/object/computed'; -import {run} from '@ember/runloop'; +import Component from '@glimmer/component'; +import {action} from '@ember/object'; -export default Component.extend(ValidationState, { - classNames: 'gh-blognav-item', - classNameBindings: ['errorClass', 'benefitItem.isNew::gh-blognav-item--sortable'], - - new: false, - - // closure actions - addItem() {}, - deleteItem() {}, - updateLabel() {}, - name: boundOneWay('benefitItem.name'), - - errors: readOnly('benefitItem.errors'), - - errorClass: computed('hasError', function () { - return this.hasError ? 'gh-blognav-item--error' : ''; - }), - - actions: { - addItem(item) { - this.addItem(item); - }, - - deleteItem(item) { - this.deleteItem(item); - }, - - updateLabel(value) { - this.set('name', value); - return this.updateLabel(value, this.benefitItem); - }, - - clearLabelErrors() { - if (this.get('benefitItem.errors')) { - this.get('benefitItem.errors').remove('name'); - } - } - }, - - keyPress(event) { - // enter key - if (event.keyCode === 13) { - event.preventDefault(); - if (this.get('benefitItem.isNew')) { - run.scheduleOnce('actions', this, this.send, 'addItem', this.benefitItem); - } else { - run.scheduleOnce('actions', this, this.send, 'focusItem', this.benefitItem); - } - } +export default class GhBenefitItem extends Component { + @action + handleLabelInput(event) { + this.updateLabel(event.target.value); } -}); + + @action + updateLabel(value) { + this.args.updateLabel(value, this.args.benefitItem); + } + + @action + clearLabelErrors() { + this.args.benefitItem.errors?.remove('name'); + } +} diff --git a/ghost/admin/app/modifiers/validation-status.js b/ghost/admin/app/modifiers/validation-status.js index 3b71f5c2df..bb8f74e553 100644 --- a/ghost/admin/app/modifiers/validation-status.js +++ b/ghost/admin/app/modifiers/validation-status.js @@ -1,12 +1,18 @@ import Modifier from 'ember-modifier'; import {isEmpty} from '@ember/utils'; -const errorClass = 'error'; -const successClass = 'success'; +const ERROR_CLASS = 'error'; +const SUCCESS_CLASS = 'success'; export default class ValidationStatusModifier extends Modifier { - modify(element, positional, {errors, property, hasValidated}) { - const validationClass = this.errorClass(errors, property, hasValidated); + modify(element, positional, {errors, property, hasValidated, errorClass = ERROR_CLASS, successClass = SUCCESS_CLASS}) { + const hasError = this.hasError(errors, property, hasValidated); + + let validationClass = ''; + + if (!property || hasValidated?.includes(property)) { + validationClass = hasError ? errorClass : successClass; + } element.classList.remove(errorClass); element.classList.remove(successClass); @@ -16,16 +22,6 @@ export default class ValidationStatusModifier extends Modifier { } } - errorClass(errors, property, hasValidated) { - const hasError = this.hasError(errors, property, hasValidated); - - if (hasValidated && hasValidated.includes(property)) { - return hasError ? errorClass : successClass; - } else { - return ''; - } - } - hasError(errors, property, hasValidated) { // if we aren't looking at a specific property we always want an error class if (!property && errors && !errors.get('isEmpty')) { diff --git a/ghost/admin/tests/integration/modifiers/validation-status-test.js b/ghost/admin/tests/integration/modifiers/validation-status-test.js new file mode 100644 index 0000000000..fe12a2aab4 --- /dev/null +++ b/ghost/admin/tests/integration/modifiers/validation-status-test.js @@ -0,0 +1,129 @@ +import DS from 'ember-data'; // eslint-disable-line +import EmberObject from '@ember/object'; +import hbs from 'htmlbars-inline-precompile'; +import {expect} from 'chai'; +import {find, render} from '@ember/test-helpers'; +import {settled} from '@ember/test-helpers'; +import {setupRenderingTest} from 'ember-mocha'; + +const {Errors} = DS; + +describe('Integration: Modifier: validation-status', function () { + setupRenderingTest(); + + this.beforeEach(function () { + let testObject = EmberObject.create(); + testObject.name = 'Test'; + testObject.hasValidated = []; + testObject.errors = Errors.create(); + + this.set('testObject', testObject); + }); + + it('handles missing params', async function () { + await render(hbs`
`); + expect(find('.test')).to.exist; + }); + + describe('with errors/property/hasValidated params', function () { + it('has no success/error class by default', async function () { + await render(hbs` +
+ `); + + expect(find('.test').classList).to.have.length(1); + }); + + it('has success class when valid', async function () { + await render(hbs` +
+ `); + + this.testObject.hasValidated.pushObject('name'); // pushObject vs push because this is an EmberArray and we're testing tracking + await settled(); + + expect(find('.test')).to.have.class('success'); + expect(find('.test')).to.not.have.class('error'); + }); + + it('has error class when invalid', async function () { + await render(hbs` +
+ `); + + this.testObject.hasValidated.pushObject('name'); // pushObject vs push because this is an EmberArray and we're testing tracking + this.testObject.errors.add('name', 'has error'); + await settled(); + + expect(find('.test')).to.have.class('error'); + expect(find('.test')).to.not.have.class('success'); + }); + + it('always has error class when no property is passed', async function () { + await render(hbs` +
+ `); + + this.testObject.hasValidated.pushObject('different'); // pushObject vs push because this is an EmberArray and we're testing tracking + this.testObject.errors.add('different', 'has error'); + await settled(); + + expect(find('.test')).to.have.class('error'); + expect(find('.test')).to.not.have.class('success'); + }); + + it('can have custom success/error classes', async function () { + await render(hbs` +
+ `); + + this.testObject.hasValidated.pushObject('name'); // pushObject vs push because this is an EmberArray and we're testing tracking + await settled(); + + expect(find('.test')).to.have.class('custom-success'); + expect(find('.test')).to.not.have.class('success'); + + this.testObject.errors.add('name', 'invalid'); + await settled(); + + expect(find('.test')).to.have.class('custom-error'); + expect(find('.test')).to.not.have.class('error'); + }); + }); +});