test and corrections

This commit is contained in:
Benjamin Dalsass
2025-11-27 10:58:21 +01:00
parent d0a2af44ac
commit b8a093e625
22 changed files with 457 additions and 119 deletions

View File

@@ -54,12 +54,3 @@
.ibo-field legend{
margin-top: 24px;
}
// to fix 3.3
.ts-wrapper{
border: 1px solid #aebecd!important;
}
.ts-dropdown{
background-color: #fff;
border: 1px solid #aebecd!important;
}

View File

@@ -1,6 +1,7 @@
class TomSelectElement extends HTMLSelectElement {
class ChoicesElement extends HTMLSelectElement {
plugins = [];
connectedCallback() {
if (this.getAttribute('multiple')) {
this.plugins.push('remove_button');
}
@@ -30,4 +31,4 @@ class TomSelectElement extends HTMLSelectElement {
}
}
customElements.define('tom-select-element', TomSelectElement, {extends: 'select'});
customElements.define('choices-element', ChoicesElement, {extends: 'select'});

View File

@@ -0,0 +1,70 @@
class OqlElement extends HTMLTextAreaElement {
// register the custom element
static{
customElements.define('oql-element', OqlElement, {extends: 'textarea'});
}
// variables
#url = '../pages/ajax.render.php?route=oql.validate_query';
#iconValid = 'fa-check-double';
#iconNotValid = 'fa-exclamation-triangle';
#debounceTimer = null;
#debounce = 300;
/** connectedCallback **/
connectedCallback() {
this.addEventListener('input', this.#onInput.bind(this));
this.#callValidateQuery();
}
/**
* Call oql verification with debounce when input event is fired.
*/
#onInput() {
if (this.#debounceTimer) clearTimeout(this.#debounceTimer);
this.#debounceTimer = setTimeout(() => {
this.#callValidateQuery(true);
}, this.#debounce);
}
/**
* Call the ajax to validate the query.
*
* @param fireChange flag to handle change event
*/
#callValidateQuery(fireChange = false) {
fetch(this.#url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Combodo-Ajax': true
},
body: JSON.stringify({
query: this.value
})
})
.then(response => response.json())
.then(response => {
// fire change event only if the query is valid
if (fireChange && response.is_valid){
this.#fireChangeEvent();
}
// update the icon color
const fieldEl = this.closest('.ibo-field');
const marqueeEl = fieldEl.querySelector('[role="marquee"]');
marqueeEl.style.color = response.is_valid ? 'green' : 'orange';
marqueeEl.classList.toggle(this.#iconNotValid, !response.is_valid);
marqueeEl.classList.toggle(this.#iconValid, response.is_valid);
});
}
/**
* Fire a change event.
*/
#fireChangeEvent() {
const changeEvent = new Event('change', { bubbles: true, cancelable: true });
this.dispatchEvent(changeEvent);
}
}

View File

@@ -3,12 +3,25 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
$aFormBlockDataTransmittedData = {};
function triggerTurbo(el) {
let sFormName = el.form.getAttribute("name");
el.form.querySelector(`[name="${sFormName}[_turbo_trigger]"]`).value = el.getAttribute('name');
el.form.setAttribute('novalidate', true);
el.form.requestSubmit();
console.log('Auto submitting form due to change in field ' + el.getAttribute('name'));
const name = el.getAttribute('name');
if(isCheckbox(el) || $aFormBlockDataTransmittedData[name] !== el.value) {
let sFormName = el.form.getAttribute("name");
el.form.querySelector(`[name="${sFormName}[_turbo_trigger]"]`).value = el.getAttribute('name');
el.form.setAttribute('novalidate', true);
el.form.requestSubmit();
$aFormBlockDataTransmittedData[name] = el.value;
}
}
function isCheckbox (element) {
return element instanceof HTMLInputElement
&& element.getAttribute('type') === 'checkbox'
}
function addFormToCollection(e) {

View File

@@ -345,6 +345,7 @@ return array(
'Combodo\\iTop\\Controller\\AjaxRenderController' => $baseDir . '/sources/Controller/AjaxRenderController.php',
'Combodo\\iTop\\Controller\\Base\\Layout\\ActivityPanelController' => $baseDir . '/sources/Controller/Base/Layout/ActivityPanelController.php',
'Combodo\\iTop\\Controller\\Base\\Layout\\ObjectController' => $baseDir . '/sources/Controller/Base/Layout/ObjectController.php',
'Combodo\\iTop\\Controller\\Base\\Layout\\OqlController' => $baseDir . '/sources/Controller/Base/Layout/OqlController.php',
'Combodo\\iTop\\Controller\\Links\\LinkSetController' => $baseDir . '/sources/Controller/Links/LinkSetController.php',
'Combodo\\iTop\\Controller\\Newsroom\\iTopNewsroomController' => $baseDir . '/sources/Controller/Newsroom/iTopNewsroomController.php',
'Combodo\\iTop\\Controller\\Notifications\\ActionController' => $baseDir . '/sources/Controller/Notifications/ActionController.php',
@@ -496,6 +497,7 @@ return array(
'Combodo\\iTop\\Forms\\Block\\Expression\\BooleanExpressionFormBlock' => $baseDir . '/sources/Forms/Block/Expression/BooleanExpressionFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Expression\\NumberExpressionFormBlock' => $baseDir . '/sources/Forms/Block/Expression/NumberExpressionFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\FormBlockException' => $baseDir . '/sources/Forms/Block/FormBlockException.php',
'Combodo\\iTop\\Forms\\Block\\FormBlockHelper' => $baseDir . '/sources/Forms/Block/FormBlockHelper.php',
'Combodo\\iTop\\Forms\\FormBuilder\\DependencyHandler' => $baseDir . '/sources/Forms/FormBuilder/DependencyHandler.php',
'Combodo\\iTop\\Forms\\FormBuilder\\DependencyMap' => $baseDir . '/sources/Forms/FormBuilder/DependencyMap.php',
'Combodo\\iTop\\Forms\\FormBuilder\\FormBuilder' => $baseDir . '/sources/Forms/FormBuilder/FormBuilder.php',
@@ -3668,5 +3670,5 @@ return array(
'privUITransactionFile' => $baseDir . '/application/transaction.class.inc.php',
'privUITransactionSession' => $baseDir . '/application/transaction.class.inc.php',
'utils' => $baseDir . '/application/utils.inc.php',
'©' => $vendorDir . '/symfony/cache/Traits/ValueWrapper.php',
'<27>' => $vendorDir . '/symfony/cache/Traits/ValueWrapper.php',
);

View File

@@ -726,6 +726,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Controller\\AjaxRenderController' => __DIR__ . '/../..' . '/sources/Controller/AjaxRenderController.php',
'Combodo\\iTop\\Controller\\Base\\Layout\\ActivityPanelController' => __DIR__ . '/../..' . '/sources/Controller/Base/Layout/ActivityPanelController.php',
'Combodo\\iTop\\Controller\\Base\\Layout\\ObjectController' => __DIR__ . '/../..' . '/sources/Controller/Base/Layout/ObjectController.php',
'Combodo\\iTop\\Controller\\Base\\Layout\\OqlController' => __DIR__ . '/../..' . '/sources/Controller/Base/Layout/OqlController.php',
'Combodo\\iTop\\Controller\\Links\\LinkSetController' => __DIR__ . '/../..' . '/sources/Controller/Links/LinkSetController.php',
'Combodo\\iTop\\Controller\\Newsroom\\iTopNewsroomController' => __DIR__ . '/../..' . '/sources/Controller/Newsroom/iTopNewsroomController.php',
'Combodo\\iTop\\Controller\\Notifications\\ActionController' => __DIR__ . '/../..' . '/sources/Controller/Notifications/ActionController.php',
@@ -877,6 +878,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Forms\\Block\\Expression\\BooleanExpressionFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Expression/BooleanExpressionFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Expression\\NumberExpressionFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Expression/NumberExpressionFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\FormBlockException' => __DIR__ . '/../..' . '/sources/Forms/Block/FormBlockException.php',
'Combodo\\iTop\\Forms\\Block\\FormBlockHelper' => __DIR__ . '/../..' . '/sources/Forms/Block/FormBlockHelper.php',
'Combodo\\iTop\\Forms\\FormBuilder\\DependencyHandler' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/DependencyHandler.php',
'Combodo\\iTop\\Forms\\FormBuilder\\DependencyMap' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/DependencyMap.php',
'Combodo\\iTop\\Forms\\FormBuilder\\FormBuilder' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/FormBuilder.php',
@@ -4049,7 +4051,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'privUITransactionFile' => __DIR__ . '/../..' . '/application/transaction.class.inc.php',
'privUITransactionSession' => __DIR__ . '/../..' . '/application/transaction.class.inc.php',
'utils' => __DIR__ . '/../..' . '/application/utils.inc.php',
'©' => __DIR__ . '/..' . '/symfony/cache/Traits/ValueWrapper.php',
'<EFBFBD>' => __DIR__ . '/..' . '/symfony/cache/Traits/ValueWrapper.php',
);
public static function getInitializer(ClassLoader $loader)

View File

@@ -182,7 +182,8 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
$this->LinkScriptFromAppRoot('node_modules/selectize-plugin-a11y/selectize-plugin-a11y.js');
$this->LinkScriptFromAppRoot('js/jquery.multiselect.js');
$this->LinkScriptFromAppRoot('node_modules/tom-select/dist/js/tom-select.complete.min.js');
$this->LinkScriptFromAppRoot('js/forms/custom-elements/tom-select.js');
$this->LinkScriptFromAppRoot('js/forms/custom-elements/choices.js');
$this->LinkScriptFromAppRoot('js/forms/custom-elements/oql.js');
// Used by inline image, CKEditor and other places
$this->LinkScriptFromAppRoot('node_modules/magnific-popup/dist/jquery.magnific-popup.min.js');

View File

@@ -0,0 +1,45 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Controller\Base\Layout;
use Combodo\iTop\Controller\AbstractController;
use Exception;
use Combodo\iTop\Application\WebPage\JsonPage;
use ModelReflection;
use ModelReflectionRuntime;
class OqlController extends AbstractController
{
public const ROUTE_NAMESPACE = 'oql';
public function OperationValidateQuery()
{
$oPage = new JsonPage();
$oPage->SetOutputDataOnly(true);
$data = json_decode(file_get_contents('php://input'), true);
$sOql = $data['query'];
try{
/** @var ModelReflection $oModelReflection */
$oModelReflexion = new ModelReflectionRuntime();
$oModelReflexion->GetQuery($sOql);
}
catch(Exception $e){
}
$oPage->SetData([
'is_valid' => !isset($e),
]);
$oPage->output();
}
}

View File

@@ -94,7 +94,7 @@ abstract class AbstractFormBlock implements IFormBlock
*/
public function IsRootBlock(): bool
{
return $this->oParent !== null;
return $this->oParent === null;
}
/**

View File

@@ -93,7 +93,8 @@ abstract class AbstractTypeFormBlock extends AbstractFormBlock
parent::UpdateOptions($oOptionsRegister);
if ($this->GetInput(self::INPUT_ENABLE)->IsBound()) {
$oOptionsRegister->SetOption('disabled', !$this->GetInputValue(self::INPUT_ENABLE));
$test = $this->GetInputValue(self::INPUT_ENABLE)->IsTrue();
$oOptionsRegister->SetOption('disabled', !$this->GetInputValue(self::INPUT_ENABLE)->IsTrue());
}
}
}

View File

@@ -21,7 +21,7 @@ class ChoiceFormBlock extends AbstractTypeFormBlock
{
// Outputs
public const OUTPUT_LABEL = 'label';
public const OUTPUT_CODE = 'code';
public const OUTPUT_VALUE = 'value';
/** @inheritdoc */
public function GetFormType(): string
@@ -34,6 +34,6 @@ class ChoiceFormBlock extends AbstractTypeFormBlock
{
parent::RegisterIO($oIORegister);
$oIORegister->AddOutput(self::OUTPUT_LABEL, StringIOFormat::class, new ChoiceValueToLabelConverter($this));
$oIORegister->AddOutput(self::OUTPUT_CODE, StringIOFormat::class);
$oIORegister->AddOutput(self::OUTPUT_VALUE, StringIOFormat::class);
}
}

View File

@@ -27,9 +27,6 @@ class AttributeValueChoiceFormBlock extends ChoiceFormBlock
public const INPUT_CLASS_NAME = 'class_name';
public const INPUT_ATTRIBUTE = 'attribute';
// Outputs
public const OUTPUT_VALUE = 'value';
/** @inheritdoc */
protected function RegisterOptions(OptionsRegister $oOptionsRegister): void
{
@@ -45,7 +42,6 @@ class AttributeValueChoiceFormBlock extends ChoiceFormBlock
parent::RegisterIO($oIORegister);
$oIORegister->AddInput(self::INPUT_CLASS_NAME, ClassIOFormat::class);
$oIORegister->AddInput(self::INPUT_ATTRIBUTE, AttributeIOFormat::class);
$oIORegister->AddOutput(self::OUTPUT_VALUE, AttributeIOFormat::class);
}
/** @inheritdoc

View File

@@ -47,7 +47,7 @@ abstract class AbstractExpressionFormBlock extends AbstractFormBlock
$aParamsToResolve = $oExpression->GetParameters();
$aResolvedParams = [];
foreach ($aParamsToResolve as $sParamToResolve) {
$aResolvedParams[$sParamToResolve] = $this->GetInputValue($sParamToResolve);
$aResolvedParams[$sParamToResolve] = strval($this->GetInputValue($sParamToResolve));
}
return $oExpression->Evaluate($aResolvedParams);
} catch (\Exception $e) {

View File

@@ -0,0 +1,25 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\Block;
class FormBlockHelper
{
/**
* Returns a unique form ID for the given form block, based on its hierarchy.
*
* @param AbstractFormBlock $oForm
*
* @return string
*/
public static function GetFormId(AbstractFormBlock $oForm): string
{
if (is_null($oForm->getParent())) {
return $oForm->getName();
}
return self::GetFormId($oForm->getParent()).'_'.$oForm->getName();
}
}

View File

@@ -45,7 +45,7 @@ class FormOutput extends AbstractFormIO
{
if (is_null($this->oConverter)) {
$sType = $this->GetDataType();
return new $sType($oData);
return $oData !== null ? new $sType($oData) : null;
}
return $this->oConverter->Convert($oData);

View File

@@ -8,8 +8,12 @@
namespace Combodo\iTop\Forms\Register;
use Combodo\iTop\Forms\Block\AbstractFormBlock;
use Combodo\iTop\Forms\Block\Base\FormBlock;
use Combodo\iTop\Forms\Block\FormBlockException;
use Combodo\iTop\Forms\Block\FormBlockHelper;
use Combodo\iTop\Forms\FormType\FormTypeHelper;
use Combodo\iTop\Forms\IO\Converter\AbstractConverter;
use Combodo\iTop\Forms\IO\FormBlockIOException;
use Combodo\iTop\Forms\IO\FormInput;
use Combodo\iTop\Forms\IO\FormOutput;
@@ -25,7 +29,7 @@ class IORegister
private array $aOutputs = [];
/**
* @param \Combodo\iTop\Forms\Block\AbstractFormBlock $oFormBlock
* @param AbstractFormBlock $oFormBlock
*/
public function __construct(private readonly AbstractFormBlock $oFormBlock)
{
@@ -35,16 +39,18 @@ class IORegister
* @param string $sName
* @param string $sType
*
* @return void
* @throws \Combodo\iTop\Forms\IO\FormBlockIOException
* @return $this
* @throws FormBlockIOException
*/
public function AddInput(string $sName, string $sType): void
public function AddInput(string $sName, string $sType): self
{
$oFormInput = new FormInput($sName, $sType, $this->oFormBlock);
if (array_key_exists($oFormInput->GetName(), $this->aInputs)) {
throw new RegisterException('Input already exists '.json_encode($oFormInput->GetName()).' for '.json_encode($this->oFormBlock->GetName()));
}
$this->aInputs[$oFormInput->GetName()] = $oFormInput;
return $this;
}
/**
@@ -55,9 +61,9 @@ class IORegister
* @param string $sOutputName
*
* @return $this
* @throws \Combodo\iTop\Forms\Block\FormBlockException
* @throws \Combodo\iTop\Forms\IO\FormBlockIOException
* @throws \Combodo\iTop\Forms\Register\RegisterException
* @throws FormBlockException
* @throws FormBlockIOException
* @throws RegisterException
*/
public function AddInputDependsOn(string $sName, string $sOutputBlockName, string $sOutputName): self
{
@@ -78,9 +84,9 @@ class IORegister
* @param string $sOutputName the dependency output name
*
* @return $this
* @throws \Combodo\iTop\Forms\Block\FormBlockException
* @throws \Combodo\iTop\Forms\IO\FormBlockIOException
* @throws \Combodo\iTop\Forms\Register\RegisterException
* @throws FormBlockException
* @throws FormBlockIOException
* @throws RegisterException
*/
public function DependsOn(string $sInputName, string $sOutputBlockName, string $sOutputName): self
{
@@ -102,9 +108,9 @@ class IORegister
* @param string $sParentOutputName parent output name
*
* @return $this
* @throws \Combodo\iTop\Forms\Block\FormBlockException
* @throws \Combodo\iTop\Forms\IO\FormBlockIOException
* @throws \Combodo\iTop\Forms\Register\RegisterException
* @throws FormBlockException
* @throws FormBlockIOException
* @throws RegisterException
*/
public function ImpactParent(string $sOutputName, string $sParentOutputName): self
{
@@ -119,7 +125,7 @@ class IORegister
{
$oFormOutput = new FormOutput($sName, $sType, $this->oFormBlock, $oConverter);
if (array_key_exists($oFormOutput->GetName(), $this->aOutputs)) {
throw new RegisterException('Output already exists '.json_encode($oFormOutput->GetName()).' for '.json_encode($this->oFormBlock->GetName()));
throw new RegisterException('Output already exists '.json_encode($oFormOutput->GetName()).' for '.json_encode($this->oFormBlock->GetName()) . ' in block ' . FormBlockHelper::GetFormId($this->oFormBlock) . ' of class ' . get_class($this->oFormBlock));
}
$this->aOutputs[$oFormOutput->GetName()] = $oFormOutput;
}
@@ -130,17 +136,29 @@ class IORegister
* @param string $sName
*
* @return FormInput
* @throws \Combodo\iTop\Forms\Register\RegisterException
* @throws RegisterException
*/
public function GetInput(string $sName): FormInput
{
if (!array_key_exists($sName, $this->aInputs)) {
if (!$this->HasInput($sName)) {
throw new RegisterException('Missing input '.json_encode($sName).' for '.json_encode($this->oFormBlock->GetName()));
}
return $this->aInputs[$sName];
}
/**
* Test input existence.
*
* @param string $sName
*
* @return bool
*/
public function HasInput(string $sName): bool
{
return array_key_exists($sName, $this->aInputs);
}
/**
* @return array
*/
@@ -189,7 +207,7 @@ class IORegister
* @param string $sName output name
*
* @return FormOutput
* @throws \Combodo\iTop\Forms\Register\RegisterException
* @throws RegisterException
*/
public function GetOutput(string $sName): FormOutput
{
@@ -326,9 +344,9 @@ class IORegister
* @param string $sParentInputName parent input name
*
* @return $this
* @throws \Combodo\iTop\Forms\Block\FormBlockException
* @throws \Combodo\iTop\Forms\IO\FormBlockIOException
* @throws \Combodo\iTop\Forms\Register\RegisterException
* @throws FormBlockException
* @throws FormBlockIOException
* @throws RegisterException
*/
public function DependsOnParent(string $sInputName, string $sParentInputName): self
{

View File

@@ -23,7 +23,7 @@
{%- block choice_widget_collapsed -%}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' ibo-input')|trim}) %}
<select is="tom-select-element" class="field_autocomplete ibo-input ibo-input-select ibo-input-select-autocomplete ui-autocomplete-input"
<select is="choices-element" class="field_autocomplete ibo-input ibo-input-select ibo-input-select-autocomplete ui-autocomplete-input" style="display: none"
{{ block('widget_attributes') }}
{% if multiple %} multiple="multiple" {% endif %}
{% if max_items_selected is defined %} data-tom-select-max-items-selected="{{ max_items_selected }}" {% endif %}
@@ -72,9 +72,6 @@
<div id="turbo_{{ block.id }}" class="ibo-field ibo-content-block ibo-block ibo-field-small">
{% if block.added == 1 %}
{{ form_row(form[block.name]) }}
{# {% else %}#}
{# {% else %}#}
{# <div style="background-color: #ecd9eb;border-radius: 6px;padding: 2px 5px;">Reserved place for <b>{{ block.name }}</b></div>#}
{% endif %}
</div>
{% endfor %}
@@ -93,19 +90,11 @@
{% endif %}
{%- endblock collection_widget -%}
{%- block form_label_content -%}
{{- parent() -}}
{% if with_ai_button is defined and with_ai_button %}
<div style="float: right;display: flex;flex-direction: column;">
<button class="ibo-button ibo-button ibo-block ibo-is-alternative ibo-is-neutral " data-role="ibo-button" type="button" name="AI" value="" data-tooltip-content="AI" aria-label="AI">
<span class="ibo-button--icon fas fa-magic"></span>
</button>
<button class="ibo-button ibo-button ibo-block ibo-is-alternative ibo-is-neutral " data-role="ibo-button" type="button" name="Book" value="" data-tooltip-content="Query Book" aria-label="AI">
<span class="ibo-button--icon fas fa-book"></span>
</button>
</div>
{% endif %}
<div style="float: right;display: flex;flex-direction: column;align-items: center;row-gap: 3px;">
<span role="marquee" class="ibo-button--icon fas" style="padding:6px;"></span>
</div>
{%-endblock form_label_content -%}
{%- block form_row -%}
@@ -118,3 +107,17 @@
{{- parent() -}}
</div>
{%- endblock form_errors -%}
{%- block oql_form_widget -%}
<textarea is="oql-element" {{ block('widget_attributes') }}>{{ value }}</textarea>
{% if with_ai_button is defined and with_ai_button %}
<button class="ibo-button ibo-button ibo-block ibo-is-primary ibo-is-neutral " data-role="ibo-button" type="button" name="AI" value="" aria-label="AI">
<span class="ibo-button--icon fas fa-magic"></span>
Use AI assistant
</button>
{% endif %}
<button class="ibo-button ibo-button ibo-block ibo-is-primary ibo-is-neutral " data-role="ibo-button" type="button" name="Book" value="" aria-label="AI">
<span class="ibo-button--icon fas fa-book"></span>
Open query phrase book
</button>
{%- endblock oql_form_widget -%}

View File

@@ -69,10 +69,10 @@
<file>../../core/apc-emulation.php</file>
<file>../../core/ormlinkset.class.inc.php</file>
<file>../../datamodels/2.x/itop-tickets/main.itop-tickets.php</file>
<file>../../sources/Forms/Block/AbstractTypeFormBlock.php</file>
<file>../../sources/Forms/Block/AbstractFormBlock.php</file>
<file>../../sources/Forms/Block/Base/FormBlock.php</file>
<!-- <folder>../../sources</folder>-->
<!-- <file>../../sources/Forms/Block/AbstractTypeFormBlock.php</file>-->
<!-- <file>../../sources/Forms/Block/AbstractFormBlock.php</file>-->
<!-- <file>../../sources/Forms/Block/Base/FormBlock.php</file>-->
<directory>../../sources/Forms/</directory>
</whitelist>
</filter>

View File

@@ -12,7 +12,9 @@ use Combodo\iTop\Forms\Block\Base\FormBlock;
use Combodo\iTop\Forms\IO\Format\StringIOFormat;
use Combodo\iTop\Forms\IO\FormInput;
use Combodo\iTop\Forms\IO\FormOutput;
use Combodo\iTop\Forms\Register\IORegister;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use ReflectionClass;
/**
* @copyright Copyright (C) 2010-2025 Combodo SARL
@@ -35,15 +37,23 @@ abstract class AbstractFormsTest extends ItopDataTestCase
return new FormOutput($sName.'_output', $sType, $oBlock);
}
public function GivenFormBlock(string $sName, string $sBlockClass = FormBlock::class): AbstractFormBlock
public function GivenFormBlock(string $sName): FormBlock
{
return new $sBlockClass($sName, []);
return new FormBlock($sName, []);
}
public function GivenSubFormBlock(AbstractFormBlock $oParent, string $sName, string $ssBlockClass = FormBlock::class): AbstractFormBlock
public function GivenSubFormBlock(FormBlock $oParent, string $sName, string $ssBlockClass = FormBlock::class): AbstractFormBlock
{
$oParent->Add($sName, $ssBlockClass, []);
return $oParent->Get($sName);
}
public function GetIORegister(AbstractFormBlock $oFormBlock): IORegister
{
$reflection = new ReflectionClass(AbstractFormBlock::class);
$reflection_property = $reflection->getProperty('oIORegister');
$reflection_property->setAccessible(true);
return $reflection_property->getValue($oFormBlock);
}
}

View File

@@ -113,56 +113,14 @@ class BlockTest extends AbstractFormsTest
$oForm->get('birthdate');
}
public function testAddingTwiceTheSameInputThrowsException(): void
public function testIsRootBlock(): void
{
$oFormBlock = $this->GivenFormBlock('OneBlock')
->AddInput('test_input', StringIOFormat::class);
/** @var FormBlock $oFormBlock */
$oFormBlock = $this->GivenFormBlock('OneBlock');
$this->expectException(RegisterException::class);
$oFormBlock->AddInput('test_input', StringIOFormat::class);
}
$oFormBlock->Add('subform', FormBlock::class);
public function testAddingTwiceTheSameOutputThrowsException(): void
{
$oFormBlock = $this->GivenFormBlock('OneBlock')
->AddOutput('test_output', StringIOFormat::class);
$this->expectException(RegisterException::class);
$oFormBlock->AddOutput('test_output', StringIOFormat::class);
}
public function testDependingOnNonExistingInputThrowsException(): void
{
$oParentBlock = $this->GivenFormBlock('ParentBlock');
$oFormBlock = $this->GivenSubFormBlock($oParentBlock, 'OneBlock')
->AddInput('test_input', StringIOFormat::class);
$this->GivenSubFormBlock($oParentBlock, 'OtherBlock')
->AddOutput('test_output', StringIOFormat::class);
$this->expectException(RegisterException::class);
$oFormBlock->DependsOn('non_existing_input', 'OtherBlock', 'test_output');
}
public function testDependingOnNonExistingOutputThrowsException(): void
{
$oParentBlock = $this->GivenFormBlock('ParentBlock');
$oFormBlock = $this->GivenSubFormBlock($oParentBlock, 'OneBlock')
->AddInput('test_input', StringIOFormat::class);
$this->GivenSubFormBlock($oParentBlock, 'OtherBlock')
->AddOutput('test_output', StringIOFormat::class);
$this->expectException(RegisterException::class);
$oFormBlock->DependsOn('test_input', 'OtherBlock', 'non_existing_output');
}
public function testDependingOnNonExistingBlockThrowsException(): void
{
$oFormBlock = $this->GivenFormBlock('OneBlock')
->AddOutput('test_output', StringIOFormat::class);
$this->expectException(RegisterException::class);
$oFormBlock->DependsOn('test_input', 'UnknownBlock', 'test');
$this->assertTrue($oFormBlock->IsRootBlock());
$this->assertFalse($oFormBlock->Get('subform')->IsRootBlock());
}
}

View File

@@ -0,0 +1,194 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Forms\Register;
use Combodo\iTop\Forms\Block\Base\CheckboxFormBlock;
use Combodo\iTop\Forms\Block\Base\FormBlock;
use Combodo\iTop\Forms\Block\Base\TextFormBlock;
use Combodo\iTop\Forms\IO\Format\BooleanIOFormat;
use Combodo\iTop\Forms\IO\Format\StringIOFormat;
use Combodo\iTop\Forms\Register\IORegister;
use Combodo\iTop\Forms\Register\RegisterException;
use Combodo\iTop\Test\UnitTest\sources\Forms\AbstractFormsTest;
class IORegisterTest extends AbstractFormsTest
{
private FormBlock $oFormBlock;
private IORegister $oIORegister;
protected function setUp(): void
{
parent::setUp();
$this->oFormBlock = $this->GivenFormBlock('OneBlock');
$this->oIORegister = $this->GetIORegister($this->oFormBlock);
}
public function testAddInput(): void
{
$this->oIORegister->AddInput('input', StringIOFormat::class);
$this->assertTrue($this->oIORegister->HasInput('input'));
$this->assertNotNull($this->oIORegister->GetInput('input'));
}
public function testGetInputs(): void
{
$iOriginInputCount = count($this->oIORegister->GetInputs());
$this->oIORegister->AddInput('input_1', StringIOFormat::class);
$this->oIORegister->AddInput('input_2', BooleanIOFormat::class);
$this->oIORegister->AddInput('input_3', StringIOFormat::class);
$this->assertCount(3 + $iOriginInputCount, $this->oIORegister->GetInputs());
$this->assertArrayHasKey('input_1', $this->oIORegister->GetInputs());
}
public function testGetOutputs(): void
{
$this->oIORegister->AddOutput('output_1', StringIOFormat::class);
$this->oIORegister->AddOutput('output_2', BooleanIOFormat::class);
$this->assertCount(2, $this->oIORegister->GetOutputs());
$this->assertArrayHasKey('output_1', $this->oIORegister->GetOutputs());
}
public function testMissingInput(): void
{
$this->expectException(RegisterException::class);
$this->oIORegister->GetInput('missing_input');
}
public function testMissingOutput(): void
{
$this->expectException(RegisterException::class);
$this->oIORegister->GetOutput('missing_output');
}
public function testGetBoundInputs(): void
{
$this->GivenSubFormBlock($this->oFormBlock, 'SubFormA', TextFormBlock::class);
$this->GivenSubFormBlock($this->oFormBlock, 'SubFormB', CheckboxFormBlock::class);
$this->GivenSubFormBlock($this->oFormBlock, 'SubFormC', TextFormBlock::class);
$oSubForm = $this->GivenSubFormBlock($this->oFormBlock, 'SubForm', TextFormBlock::class);
$oSubForm->AddInput('input_from_A', StringIOFormat::class);
$oSubForm->AddInput('input_from_B', BooleanIOFormat::class);
$oSubForm->AddInput('input_from_C', StringIOFormat::class);
$oSubForm->AddInput('unbound_input', StringIOFormat::class);
$this->GetIORegister($oSubForm)->DependsOn('input_from_A', 'SubFormA', TextFormBlock::OUTPUT_TEXT);
$this->GetIORegister($oSubForm)->DependsOn('input_from_B', 'SubFormB', CheckboxFormBlock::OUTPUT_CHECKED);
$this->GetIORegister($oSubForm)->DependsOn('input_from_C', 'SubFormC', TextFormBlock::OUTPUT_TEXT);
$aBoundInputs = $this->GetIORegister($oSubForm)->GetBoundInputs();
$this->assertCount(3, $aBoundInputs);
}
public function testGetBoundOutputs(): void
{
$this->oFormBlock->AddOutput('output', StringIOFormat::class);
$oSubFormA = $this->GivenSubFormBlock($this->oFormBlock, 'SubFormA', TextFormBlock::class);
$oIORegisterA = $this->GetIORegister($oSubFormA);
$oIORegisterA->ImpactParent(TextFormBlock::OUTPUT_TEXT, 'output');
$this->assertCount(1, $this->oIORegister->GetBoundOutputs());
}
public function testAddInputDependsOn(): void
{
$this->GivenSubFormBlock($this->oFormBlock, 'SubFormA', TextFormBlock::class);
$oSubFormB = $this->GivenSubFormBlock($this->oFormBlock, 'SubFormB', TextFormBlock::class);
$oIORegisterB = $this->GetIORegister($oSubFormB);
$oIORegisterB->AddInputDependsOn('input', 'SubFormA', TextFormBlock::OUTPUT_TEXT);
$this->assertNotNull($oIORegisterB->GetInput('input'));
}
public function testImpactParent(): void
{
$this->oFormBlock->AddOutput('output', StringIOFormat::class);
$oSubFormA = $this->GivenSubFormBlock($this->oFormBlock, 'SubFormA', TextFormBlock::class);
$oIORegisterA = $this->GetIORegister($oSubFormA);
$oIORegisterA->ImpactParent(TextFormBlock::OUTPUT_TEXT, 'output');
$this->assertTrue($this->oFormBlock->GetOutput('output')->IsBound());
}
public function testAddingTwiceTheSameInputThrowsException(): void
{
$this->oIORegister->AddInput('test_input', StringIOFormat::class);
$this->expectException(RegisterException::class);
$this->oIORegister->AddInput('test_input', StringIOFormat::class);
}
public function testAddingTwiceTheSameOutputThrowsException(): void
{
$this->oIORegister->AddOutput('test_output', StringIOFormat::class);
$this->expectException(RegisterException::class);
$this->oIORegister->AddOutput('test_output', StringIOFormat::class);
}
public function testDependingOnNonExistingInputThrowsException(): void
{
$this->oIORegister->AddInput('test_input', StringIOFormat::class);
$this->oIORegister->AddOutput('test_output', StringIOFormat::class);
$this->expectException(RegisterException::class);
$this->oIORegister->DependsOn('non_existing_input', 'OtherBlock', 'test_output');
}
public function testDependingOnNonExistingOutputThrowsException(): void
{
$this->oIORegister->AddInput('test_input', StringIOFormat::class);
$this->expectException(RegisterException::class);
$this->oIORegister->DependsOn('test_input', 'OtherBlock', 'non_existing_output');
}
public function testDependingOnNonExistingBlockThrowsException(): void
{
$this->oIORegister->AddInput('test_input', StringIOFormat::class);
$this->oIORegister->AddOutput('test_output', StringIOFormat::class);
$this->expectException(RegisterException::class);
$this->oIORegister->DependsOn('test_input', 'UnknownBlock', 'test');
}
public function testHasDependenciesBlocks(): void
{
$this->GivenSubFormBlock($this->oFormBlock, 'SubFormA', TextFormBlock::class);
$oSubForm = $this->GivenSubFormBlock($this->oFormBlock, 'SubForm', TextFormBlock::class);
$oSubForm->AddInput('input_from_A', StringIOFormat::class);
$this->GetIORegister($oSubForm)->DependsOn('input_from_A', 'SubFormA', TextFormBlock::OUTPUT_TEXT);
$this->assertTrue($this->GetIORegister($oSubForm)->HasDependenciesBlocks());
$this->assertFalse($this->oIORegister->HasDependenciesBlocks());
}
public function testImpactBlocks(): void
{
$oSubFormA = $this->GivenSubFormBlock($this->oFormBlock, 'SubFormA', TextFormBlock::class);
$oSubForm = $this->GivenSubFormBlock($this->oFormBlock, 'SubForm', TextFormBlock::class);
$oSubForm->AddInput('input_from_A', StringIOFormat::class);
$this->GetIORegister($oSubForm)->DependsOn('input_from_A', 'SubFormA', TextFormBlock::OUTPUT_TEXT);
$this->assertFalse($this->GetIORegister($oSubForm)->ImpactDependentsBlocks());
$this->assertTrue($this->GetIORegister($oSubFormA)->ImpactDependentsBlocks());
}
}

View File

@@ -30,6 +30,14 @@ class OptionsRegisterTest extends ItopDataTestCase
$this->oOptionsRegister->SetOption('not valid option name', 'value');
}
public function testSetOptionTwice(): void
{
$this->oOptionsRegister->SetOption('valid_option_name', 'value');
$this->oOptionsRegister->SetOption('valid_option_name', 'value2');
$this->assertEquals('value2', $this->oOptionsRegister->GetOption('valid_option_name'));
}
public function testSetNonTypeOption(): void
{
$this->oOptionsRegister->SetOption('not_a_type_option', 'value', false);