From d668e6d6013b27eaee3c6cd74afb701d07abb7d7 Mon Sep 17 00:00:00 2001 From: Tim Fischbach Date: Fri, 30 Jan 2026 17:46:21 +0100 Subject: [PATCH] Support visibility and disabled bindings in sub views ColorSelectOrCustomColorInputView uses internal sub-views with a different model. Pass binding options to these sub-views with the correct binding model so that visibleBinding and disabledBinding options work as expected. Handle all binding option variants: - visible/disabled as function or boolean without binding - visibleBinding/disabledBinding with bindingValue - visibleBinding/disabledBinding with visible/disabled function - Custom binding models REDMINE-21218 --- .../ColorSelectOrCustomColorInputView-spec.js | 405 ++++++++++++++++++ .../ColorSelectOrCustomColorInputView.js | 31 ++ 2 files changed, 436 insertions(+) diff --git a/entry_types/scrolled/package/spec/editor/views/inputs/ColorSelectOrCustomColorInputView-spec.js b/entry_types/scrolled/package/spec/editor/views/inputs/ColorSelectOrCustomColorInputView-spec.js index f4e3d0c98b..c47be053c2 100644 --- a/entry_types/scrolled/package/spec/editor/views/inputs/ColorSelectOrCustomColorInputView-spec.js +++ b/entry_types/scrolled/package/spec/editor/views/inputs/ColorSelectOrCustomColorInputView-spec.js @@ -7,6 +7,7 @@ import userEvent from '@testing-library/user-event'; import '@testing-library/jest-dom/extend-expect'; import {useFakeTranslations} from 'pageflow/testHelpers'; import {renderReactBasedBackboneView as render} from 'pageflow-scrolled/testHelpers'; +import 'support/toBeVisibleViaBinding'; describe('ColorSelectOrCustomColorInputView', () => { useFakeTranslations({ @@ -157,4 +158,408 @@ describe('ColorSelectOrCustomColorInputView', () => { expect(getByRole('option', {name: 'Auto'})).not.toBeNull(); }); + + describe('visibleBinding', () => { + it('hides select input based on visibleBinding', () => { + const model = new Backbone.Model({ + color: 'brand-red', + hidden: true + }); + + const inputView = new ColorSelectOrCustomColorInputView({ + model, + label: 'Background Color', + customColorTranslationKey: 'pageflow_scrolled.editor.custom_color', + values: ['brand-red', 'brand-green'], + texts: ['Red', 'Green'], + propertyName: 'color', + visibleBinding: 'hidden', + visibleBindingValue: false + }); + + const {getByRole} = render(inputView); + + expect(getByRole('button', {name: 'Red'})).not.toBeVisibleViaBinding(); + }); + + it('shows select input when visibleBinding condition is met', () => { + const model = new Backbone.Model({ + color: 'brand-red', + hidden: false + }); + + const inputView = new ColorSelectOrCustomColorInputView({ + model, + label: 'Background Color', + customColorTranslationKey: 'pageflow_scrolled.editor.custom_color', + values: ['brand-red', 'brand-green'], + texts: ['Red', 'Green'], + propertyName: 'color', + visibleBinding: 'hidden', + visibleBindingValue: false + }); + + const {getByRole} = render(inputView); + + expect(getByRole('button', {name: 'Red'})).toBeVisibleViaBinding(); + }); + + it('hides custom color input based on visibleBinding', () => { + const model = new Backbone.Model({ + color: '#ff0000', + hidden: true + }); + + const inputView = new ColorSelectOrCustomColorInputView({ + model, + label: 'Background Color', + customColorTranslationKey: 'pageflow_scrolled.editor.custom_color', + values: ['brand-red', 'brand-green'], + texts: ['Red', 'Green'], + propertyName: 'color', + visibleBinding: 'hidden', + visibleBindingValue: false + }); + + const {getByRole} = render(inputView); + + expect(getByRole('textbox')).not.toBeVisibleViaBinding(); + }); + + it('shows custom color input when visibleBinding condition is met', () => { + const model = new Backbone.Model({ + color: '#ff0000', + hidden: false + }); + + const inputView = new ColorSelectOrCustomColorInputView({ + model, + label: 'Background Color', + customColorTranslationKey: 'pageflow_scrolled.editor.custom_color', + values: ['brand-red', 'brand-green'], + texts: ['Red', 'Green'], + propertyName: 'color', + visibleBinding: 'hidden', + visibleBindingValue: false + }); + + const {getByRole} = render(inputView); + + expect(getByRole('textbox')).toBeVisibleViaBinding(); + }); + + it('uses custom visibleBindingModel for select input', () => { + const model = new Backbone.Model({ + color: 'brand-red' + }); + const bindingModel = new Backbone.Model({ + hidden: true + }); + + const inputView = new ColorSelectOrCustomColorInputView({ + model, + label: 'Background Color', + customColorTranslationKey: 'pageflow_scrolled.editor.custom_color', + values: ['brand-red', 'brand-green'], + texts: ['Red', 'Green'], + propertyName: 'color', + visibleBinding: 'hidden', + visibleBindingValue: false, + visibleBindingModel: bindingModel + }); + + const {getByRole} = render(inputView); + + expect(getByRole('button', {name: 'Red'})).not.toBeVisibleViaBinding(); + }); + + it('uses custom visibleBindingModel for custom color input', () => { + const model = new Backbone.Model({ + color: '#ff0000' + }); + const bindingModel = new Backbone.Model({ + hidden: true + }); + + const inputView = new ColorSelectOrCustomColorInputView({ + model, + label: 'Background Color', + customColorTranslationKey: 'pageflow_scrolled.editor.custom_color', + values: ['brand-red', 'brand-green'], + texts: ['Red', 'Green'], + propertyName: 'color', + visibleBinding: 'hidden', + visibleBindingValue: false, + visibleBindingModel: bindingModel + }); + + const {getByRole} = render(inputView); + + expect(getByRole('textbox')).not.toBeVisibleViaBinding(); + }); + + it('supports visible function without visibleBinding', () => { + const model = new Backbone.Model({ + color: 'brand-red' + }); + + const inputView = new ColorSelectOrCustomColorInputView({ + model, + label: 'Background Color', + customColorTranslationKey: 'pageflow_scrolled.editor.custom_color', + values: ['brand-red', 'brand-green'], + texts: ['Red', 'Green'], + propertyName: 'color', + visible: () => false + }); + + const {getByRole} = render(inputView); + + expect(getByRole('button', {name: 'Red'})).not.toBeVisibleViaBinding(); + }); + + it('supports visible function for custom color input', () => { + const model = new Backbone.Model({ + color: '#ff0000' + }); + + const inputView = new ColorSelectOrCustomColorInputView({ + model, + label: 'Background Color', + customColorTranslationKey: 'pageflow_scrolled.editor.custom_color', + values: ['brand-red', 'brand-green'], + texts: ['Red', 'Green'], + propertyName: 'color', + visible: () => false + }); + + const {getByRole} = render(inputView); + + expect(getByRole('textbox')).not.toBeVisibleViaBinding(); + }); + + it('supports visibleBinding with visible function instead of visibleBindingValue', () => { + const model = new Backbone.Model({ + color: 'brand-red', + state: 'locked' + }); + + const inputView = new ColorSelectOrCustomColorInputView({ + model, + label: 'Background Color', + customColorTranslationKey: 'pageflow_scrolled.editor.custom_color', + values: ['brand-red', 'brand-green'], + texts: ['Red', 'Green'], + propertyName: 'color', + visibleBinding: 'state', + visible: state => state !== 'locked' + }); + + const {getByRole} = render(inputView); + + expect(getByRole('button', {name: 'Red'})).not.toBeVisibleViaBinding(); + }); + }); + + describe('disabledBinding', () => { + it('disables select input based on disabledBinding', () => { + const model = new Backbone.Model({ + color: 'brand-red', + locked: true + }); + + const inputView = new ColorSelectOrCustomColorInputView({ + model, + label: 'Background Color', + customColorTranslationKey: 'pageflow_scrolled.editor.custom_color', + values: ['brand-red', 'brand-green'], + texts: ['Red', 'Green'], + propertyName: 'color', + disabledBinding: 'locked', + disabledBindingValue: true + }); + + const {getByRole} = render(inputView); + + expect(getByRole('button', {name: 'Red'}).closest('.input')).toHaveClass('input-disabled'); + }); + + it('enables select input when disabledBinding condition is not met', () => { + const model = new Backbone.Model({ + color: 'brand-red', + locked: false + }); + + const inputView = new ColorSelectOrCustomColorInputView({ + model, + label: 'Background Color', + customColorTranslationKey: 'pageflow_scrolled.editor.custom_color', + values: ['brand-red', 'brand-green'], + texts: ['Red', 'Green'], + propertyName: 'color', + disabledBinding: 'locked', + disabledBindingValue: true + }); + + const {getByRole} = render(inputView); + + expect(getByRole('button', {name: 'Red'}).closest('.input')).not.toHaveClass('input-disabled'); + }); + + it('disables custom color input based on disabledBinding', () => { + const model = new Backbone.Model({ + color: '#ff0000', + locked: true + }); + + const inputView = new ColorSelectOrCustomColorInputView({ + model, + label: 'Background Color', + customColorTranslationKey: 'pageflow_scrolled.editor.custom_color', + values: ['brand-red', 'brand-green'], + texts: ['Red', 'Green'], + propertyName: 'color', + disabledBinding: 'locked', + disabledBindingValue: true + }); + + const {getByRole} = render(inputView); + + expect(getByRole('textbox').closest('.input')).toHaveClass('input-disabled'); + }); + + it('enables custom color input when disabledBinding condition is not met', () => { + const model = new Backbone.Model({ + color: '#ff0000', + locked: false + }); + + const inputView = new ColorSelectOrCustomColorInputView({ + model, + label: 'Background Color', + customColorTranslationKey: 'pageflow_scrolled.editor.custom_color', + values: ['brand-red', 'brand-green'], + texts: ['Red', 'Green'], + propertyName: 'color', + disabledBinding: 'locked', + disabledBindingValue: true + }); + + const {getByRole} = render(inputView); + + expect(getByRole('textbox').closest('.input')).not.toHaveClass('input-disabled'); + }); + + it('uses custom disabledBindingModel for select input', () => { + const model = new Backbone.Model({ + color: 'brand-red' + }); + const bindingModel = new Backbone.Model({ + locked: true + }); + + const inputView = new ColorSelectOrCustomColorInputView({ + model, + label: 'Background Color', + customColorTranslationKey: 'pageflow_scrolled.editor.custom_color', + values: ['brand-red', 'brand-green'], + texts: ['Red', 'Green'], + propertyName: 'color', + disabledBinding: 'locked', + disabledBindingValue: true, + disabledBindingModel: bindingModel + }); + + const {getByRole} = render(inputView); + + expect(getByRole('button', {name: 'Red'}).closest('.input')).toHaveClass('input-disabled'); + }); + + it('uses custom disabledBindingModel for custom color input', () => { + const model = new Backbone.Model({ + color: '#ff0000' + }); + const bindingModel = new Backbone.Model({ + locked: true + }); + + const inputView = new ColorSelectOrCustomColorInputView({ + model, + label: 'Background Color', + customColorTranslationKey: 'pageflow_scrolled.editor.custom_color', + values: ['brand-red', 'brand-green'], + texts: ['Red', 'Green'], + propertyName: 'color', + disabledBinding: 'locked', + disabledBindingValue: true, + disabledBindingModel: bindingModel + }); + + const {getByRole} = render(inputView); + + expect(getByRole('textbox').closest('.input')).toHaveClass('input-disabled'); + }); + + it('supports disabled function without disabledBinding', () => { + const model = new Backbone.Model({ + color: 'brand-red' + }); + + const inputView = new ColorSelectOrCustomColorInputView({ + model, + label: 'Background Color', + customColorTranslationKey: 'pageflow_scrolled.editor.custom_color', + values: ['brand-red', 'brand-green'], + texts: ['Red', 'Green'], + propertyName: 'color', + disabled: () => true + }); + + const {getByRole} = render(inputView); + + expect(getByRole('button', {name: 'Red'}).closest('.input')).toHaveClass('input-disabled'); + }); + + it('supports disabled function for custom color input', () => { + const model = new Backbone.Model({ + color: '#ff0000' + }); + + const inputView = new ColorSelectOrCustomColorInputView({ + model, + label: 'Background Color', + customColorTranslationKey: 'pageflow_scrolled.editor.custom_color', + values: ['brand-red', 'brand-green'], + texts: ['Red', 'Green'], + propertyName: 'color', + disabled: () => true + }); + + const {getByRole} = render(inputView); + + expect(getByRole('textbox').closest('.input')).toHaveClass('input-disabled'); + }); + + it('supports disabledBinding with disabled function instead of disabledBindingValue', () => { + const model = new Backbone.Model({ + color: 'brand-red', + state: 'locked' + }); + + const inputView = new ColorSelectOrCustomColorInputView({ + model, + label: 'Background Color', + customColorTranslationKey: 'pageflow_scrolled.editor.custom_color', + values: ['brand-red', 'brand-green'], + texts: ['Red', 'Green'], + propertyName: 'color', + disabledBinding: 'state', + disabled: state => state === 'locked' + }); + + const {getByRole} = render(inputView); + + expect(getByRole('button', {name: 'Red'}).closest('.input')).toHaveClass('input-disabled'); + }); + }); }); diff --git a/entry_types/scrolled/package/src/editor/views/inputs/ColorSelectOrCustomColorInputView.js b/entry_types/scrolled/package/src/editor/views/inputs/ColorSelectOrCustomColorInputView.js index 9f2220ab27..71717bee5d 100644 --- a/entry_types/scrolled/package/src/editor/views/inputs/ColorSelectOrCustomColorInputView.js +++ b/entry_types/scrolled/package/src/editor/views/inputs/ColorSelectOrCustomColorInputView.js @@ -39,9 +39,39 @@ export const ColorSelectOrCustomColorInputView = Marionette.View.extend({ }); }, + getBindingOptions() { + return { + ...this.getBindingOptionsFor('visible'), + ...this.getBindingOptionsFor('disabled') + }; + }, + + getBindingOptionsFor(optionName) { + const result = {}; + const bindingKey = `${optionName}Binding`; + const bindingValueKey = `${optionName}BindingValue`; + const bindingModelKey = `${optionName}BindingModel`; + + if (optionName in this.options) { + result[optionName] = this.options[optionName]; + } + + if (this.options[bindingKey]) { + result[bindingKey] = this.options[bindingKey]; + result[bindingModelKey] = this.options[bindingModelKey] || this.model; + + if (bindingValueKey in this.options) { + result[bindingValueKey] = this.options[bindingValueKey]; + } + } + + return result; + }, + render() { this.colorSelectInputView = new ColorSelectInputView({ ...this.options, + ...this.getBindingOptions(), label: this.labelText(), model: this.viewModel, propertyName: 'value', @@ -66,6 +96,7 @@ export const ColorSelectOrCustomColorInputView = Marionette.View.extend({ if (customColor && !this.colorInputView) { this.colorInputView = new ColorInputView({ + ...this.getBindingOptions(), model: this.viewModel, propertyName: 'color' });