From 8a9d0a30fe4df303081cb9ddb50e089bc0f6b69b Mon Sep 17 00:00:00 2001 From: kn Date: Thu, 5 Nov 2026 15:30:19 +0100 Subject: [PATCH] Keep posted form values for the event that posted them A posted form value is written to the session client variable .value, which every content poll of a sync:value edit overwrites with the bound parameter. A poll that lands between the payload write and the handler's GetUIProperty hands the handler the stored parameter instead of the value the user submitted, so the event silently runs with the old value. handleEvent now also snapshots each posted form value into a per-event CUIEventFormValues, and CUIEnvironment's GetUIProperty* read "value" of a posted entity from that snapshot. The snapshot copies the parameter types from the client variables, so typed reads convert as before. Writing the value with SetUIProperty* during the event drops the snapshot, so a handler still reads back what it set. No ACT change. --- .../LibMCEnv/libmcenv_uienvironment.cpp | 78 +++----- .../LibMCEnv/libmcenv_uienvironment.hpp | 10 + Implementation/UI/amc_ui_eventformvalues.cpp | 103 ++++++++++ Implementation/UI/amc_ui_eventformvalues.hpp | 87 ++++++++ Implementation/UI/amc_ui_handler.cpp | 6 + Implementation/UnitTest/amc_unittests.cpp | 2 + .../amc_unittests_eventformvalues.hpp | 186 ++++++++++++++++++ 7 files changed, 425 insertions(+), 47 deletions(-) create mode 100644 Implementation/UI/amc_ui_eventformvalues.cpp create mode 100644 Implementation/UI/amc_ui_eventformvalues.hpp create mode 100644 Implementation/UnitTest/amc_unittests_eventformvalues.hpp diff --git a/Implementation/LibMCEnv/libmcenv_uienvironment.cpp b/Implementation/LibMCEnv/libmcenv_uienvironment.cpp index 42f51c3a..e8835c94 100644 --- a/Implementation/LibMCEnv/libmcenv_uienvironment.cpp +++ b/Implementation/LibMCEnv/libmcenv_uienvironment.cpp @@ -129,6 +129,7 @@ CUIEnvironment::CUIEnvironment(AMC::CUIHandler* pUIHandler, const std::string& s m_pUISystemState = pUIHandler->getUISystemState(); m_pLogger = m_pUISystemState->getLogger(); + m_pEventFormValues = std::make_shared(m_pUISystemState->getGlobalChronoInstance()); if (!sSenderUUID.empty()) { m_sSenderUUID = (AMCCommon::CUtils::normalizeUUIDString(sSenderUUID)); @@ -373,74 +374,69 @@ LibMCEnv::eParameterDataType CUIEnvironment::GetMachineParameterGroupParameterTy } -std::string CUIEnvironment::GetUIProperty(const std::string& sElementPath, const std::string& sPropertyName) -{ +AMC::PParameterGroup CUIEnvironment::getUIPropertyGroupForRead(const std::string& sElementPath, const std::string& sPropertyName) +{ auto pClientVariableHandler = m_pAPIAuth->getLegacyParameterHandler(true); if (pClientVariableHandler == nullptr) - throw ELibMCEnvInterfaceException (LIBMCENV_ERROR_COULDNNOTACCESSCLIENTVARIABLES); + throw ELibMCEnvInterfaceException(LIBMCENV_ERROR_COULDNNOTACCESSCLIENTVARIABLES); - auto pGroup = pClientVariableHandler->findGroup(sElementPath, true); - return pGroup->getParameterValueByName(sPropertyName); + return m_pEventFormValues->groupForRead(pClientVariableHandler, sElementPath, sPropertyName); } -std::string CUIEnvironment::GetUIPropertyAsUUID(const std::string& sElementPath, const std::string& sPropertyName) +AMC::PParameterGroup CUIEnvironment::getUIPropertyGroupForWrite(const std::string& sElementPath, const std::string& sPropertyName) { auto pClientVariableHandler = m_pAPIAuth->getLegacyParameterHandler(true); if (pClientVariableHandler == nullptr) throw ELibMCEnvInterfaceException(LIBMCENV_ERROR_COULDNNOTACCESSCLIENTVARIABLES); - auto pGroup = pClientVariableHandler->findGroup(sElementPath, true); + return m_pEventFormValues->groupForWrite(pClientVariableHandler, sElementPath, sPropertyName); +} + +void CUIEnvironment::capturePostedFormValue(const std::string& sElementPath, AMC::PParameterGroup pClientVariableGroup, const std::string& sPostedValue) +{ + m_pEventFormValues->capturePostedValue(sElementPath, pClientVariableGroup, sPostedValue); +} + +std::string CUIEnvironment::GetUIProperty(const std::string& sElementPath, const std::string& sPropertyName) +{ + auto pGroup = getUIPropertyGroupForRead(sElementPath, sPropertyName); + return pGroup->getParameterValueByName(sPropertyName); +} + +std::string CUIEnvironment::GetUIPropertyAsUUID(const std::string& sElementPath, const std::string& sPropertyName) +{ + auto pGroup = getUIPropertyGroupForRead(sElementPath, sPropertyName); return pGroup->getParameterValueByName(sPropertyName); } LibMCEnv_double CUIEnvironment::GetUIPropertyAsDouble(const std::string& sElementPath, const std::string& sPropertyName) { - auto pClientVariableHandler = m_pAPIAuth->getLegacyParameterHandler(true); - if (pClientVariableHandler == nullptr) - throw ELibMCEnvInterfaceException(LIBMCENV_ERROR_COULDNNOTACCESSCLIENTVARIABLES); - - auto pGroup = pClientVariableHandler->findGroup(sElementPath, true); + auto pGroup = getUIPropertyGroupForRead(sElementPath, sPropertyName); return pGroup->getDoubleParameterValueByName(sPropertyName); } LibMCEnv_int64 CUIEnvironment::GetUIPropertyAsInteger(const std::string& sElementPath, const std::string& sPropertyName) { - auto pClientVariableHandler = m_pAPIAuth->getLegacyParameterHandler(true); - if (pClientVariableHandler == nullptr) - throw ELibMCEnvInterfaceException(LIBMCENV_ERROR_COULDNNOTACCESSCLIENTVARIABLES); - - auto pGroup = pClientVariableHandler->findGroup(sElementPath, true); + auto pGroup = getUIPropertyGroupForRead(sElementPath, sPropertyName); return pGroup->getIntParameterValueByName(sPropertyName); } bool CUIEnvironment::GetUIPropertyAsBool(const std::string& sElementPath, const std::string& sPropertyName) { - auto pClientVariableHandler = m_pAPIAuth->getLegacyParameterHandler(true); - if (pClientVariableHandler == nullptr) - throw ELibMCEnvInterfaceException(LIBMCENV_ERROR_COULDNNOTACCESSCLIENTVARIABLES); - - auto pGroup = pClientVariableHandler->findGroup(sElementPath, true); + auto pGroup = getUIPropertyGroupForRead(sElementPath, sPropertyName); return pGroup->getBoolParameterValueByName(sPropertyName); } void CUIEnvironment::SetUIProperty(const std::string& sElementPath, const std::string& sPropertyName, const std::string& sValue) { - auto pClientVariableHandler = m_pAPIAuth->getLegacyParameterHandler(true); - if (pClientVariableHandler == nullptr) - throw ELibMCEnvInterfaceException(LIBMCENV_ERROR_COULDNNOTACCESSCLIENTVARIABLES); - - auto pGroup = pClientVariableHandler->findGroup(sElementPath, true); + auto pGroup = getUIPropertyGroupForWrite(sElementPath, sPropertyName); pGroup->setParameterValueByName(sPropertyName, sValue); } void CUIEnvironment::SetUIPropertyAsUUID(const std::string& sElementPath, const std::string& sPropertyName, const std::string& sValue) { - auto pClientVariableHandler = m_pAPIAuth->getLegacyParameterHandler(true); - if (pClientVariableHandler == nullptr) - throw ELibMCEnvInterfaceException(LIBMCENV_ERROR_COULDNNOTACCESSCLIENTVARIABLES); - - auto pGroup = pClientVariableHandler->findGroup(sElementPath, true); + auto pGroup = getUIPropertyGroupForWrite(sElementPath, sPropertyName); if (!sValue.empty()) pGroup->setParameterValueByName(sPropertyName, AMCCommon::CUtils::normalizeUUIDString (sValue)); else @@ -449,32 +445,20 @@ void CUIEnvironment::SetUIPropertyAsUUID(const std::string& sElementPath, const void CUIEnvironment::SetUIPropertyAsDouble(const std::string& sElementPath, const std::string& sPropertyName, const LibMCEnv_double dValue) { - auto pClientVariableHandler = m_pAPIAuth->getLegacyParameterHandler(true); - if (pClientVariableHandler == nullptr) - throw ELibMCEnvInterfaceException(LIBMCENV_ERROR_COULDNNOTACCESSCLIENTVARIABLES); - - auto pGroup = pClientVariableHandler->findGroup(sElementPath, true); + auto pGroup = getUIPropertyGroupForWrite(sElementPath, sPropertyName); pGroup->setDoubleParameterValueByName(sPropertyName, dValue); } void CUIEnvironment::SetUIPropertyAsInteger(const std::string& sElementPath, const std::string& sPropertyName, const LibMCEnv_int64 nValue) { - auto pClientVariableHandler = m_pAPIAuth->getLegacyParameterHandler(true); - if (pClientVariableHandler == nullptr) - throw ELibMCEnvInterfaceException(LIBMCENV_ERROR_COULDNNOTACCESSCLIENTVARIABLES); - - auto pGroup = pClientVariableHandler->findGroup(sElementPath, true); + auto pGroup = getUIPropertyGroupForWrite(sElementPath, sPropertyName); pGroup->setIntParameterValueByName(sPropertyName, nValue); } void CUIEnvironment::SetUIPropertyAsBool(const std::string& sElementPath, const std::string& sPropertyName, const bool bValue) { - auto pClientVariableHandler = m_pAPIAuth->getLegacyParameterHandler(true); - if (pClientVariableHandler == nullptr) - throw ELibMCEnvInterfaceException(LIBMCENV_ERROR_COULDNNOTACCESSCLIENTVARIABLES); - - auto pGroup = pClientVariableHandler->findGroup(sElementPath, true); + auto pGroup = getUIPropertyGroupForWrite(sElementPath, sPropertyName); pGroup->setBoolParameterValueByName(sPropertyName, bValue); } diff --git a/Implementation/LibMCEnv/libmcenv_uienvironment.hpp b/Implementation/LibMCEnv/libmcenv_uienvironment.hpp index cb3f97c8..480e01c1 100644 --- a/Implementation/LibMCEnv/libmcenv_uienvironment.hpp +++ b/Implementation/LibMCEnv/libmcenv_uienvironment.hpp @@ -44,6 +44,7 @@ Abstract: This is the class declaration of CUIEnvironment #include "amc_userinformation.hpp" #include "amc_ui_systemstate.hpp" #include "amc_api_auth.hpp" +#include "amc_ui_eventformvalues.hpp" #include @@ -90,6 +91,12 @@ class CUIEnvironment : public virtual IUIEnvironment, public virtual CBase { // Client session that resolves session and user references of formulas; null without a session. AMC::PUIFrontendState getFormulaFrontendState(); + // Form values posted with the event being handled. + AMC::PUIEventFormValues m_pEventFormValues; + + AMC::PParameterGroup getUIPropertyGroupForRead(const std::string& sElementPath, const std::string& sPropertyName); + AMC::PParameterGroup getUIPropertyGroupForWrite(const std::string& sElementPath, const std::string& sPropertyName); + protected: public: @@ -213,6 +220,9 @@ class CUIEnvironment : public virtual IUIEnvironment, public virtual CBase { void setExternalEventParameters(const std::string & sParametersJSON); + // Keeps sPostedValue as this event's value of sElementPath, whatever polls write to the client variable meanwhile. + void capturePostedFormValue(const std::string& sElementPath, AMC::PParameterGroup pClientVariableGroup, const std::string& sPostedValue); + std::string getExternalEventReturnValues(); ITestEnvironment* GetTestEnvironment() override; diff --git a/Implementation/UI/amc_ui_eventformvalues.cpp b/Implementation/UI/amc_ui_eventformvalues.cpp new file mode 100644 index 00000000..1d666e44 --- /dev/null +++ b/Implementation/UI/amc_ui_eventformvalues.cpp @@ -0,0 +1,103 @@ +/*++ + +Copyright (C) 2026 Autodesk Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Autodesk Inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL AUTODESK INC. BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + + +#include "amc_ui_eventformvalues.hpp" +#include "amc_parametergroup.hpp" +#include "amc_parameterhandler.hpp" + +#include "libmc_exceptiontypes.hpp" + +namespace AMC { + + CUIEventFormValues::CUIEventFormValues(AMCCommon::PChrono pGlobalChrono) + : m_pGlobalChrono(pGlobalChrono) + { + LibMCAssertNotNull(m_pGlobalChrono.get()); + } + + CUIEventFormValues::~CUIEventFormValues() + { + } + + PParameterGroup CUIEventFormValues::findPostedGroup(const std::string& sElementPath, const std::string& sPropertyName) + { + if (sPropertyName != AMC_UI_EVENTFORMVALUE_PROPERTY) + return nullptr; + + auto iIter = m_PostedGroups.find(sElementPath); + if (iIter == m_PostedGroups.end()) + return nullptr; + + return iIter->second; + } + + void CUIEventFormValues::capturePostedValue(const std::string& sElementPath, PParameterGroup pClientVariableGroup, const std::string& sPostedValue) + { + LibMCAssertNotNull(pClientVariableGroup.get()); + + // The copy takes the parameter types from the client variables; the value is set from the + // payload, not read back, so a poll that already overwrote the client variable is harmless. + auto pPostedGroup = std::make_shared(sElementPath, "posted form values", m_pGlobalChrono); + pPostedGroup->addDuplicatesFromGroup(pClientVariableGroup.get()); + pPostedGroup->setParameterValueByName(AMC_UI_EVENTFORMVALUE_PROPERTY, sPostedValue); + + std::lock_guard lockGuard(m_Mutex); + m_PostedGroups[sElementPath] = pPostedGroup; + } + + PParameterGroup CUIEventFormValues::groupForRead(CParameterHandler* pClientVariableHandler, const std::string& sElementPath, const std::string& sPropertyName) + { + LibMCAssertNotNull(pClientVariableHandler); + + { + std::lock_guard lockGuard(m_Mutex); + auto pPostedGroup = findPostedGroup(sElementPath, sPropertyName); + if (pPostedGroup.get() != nullptr) + return pPostedGroup; + } + + return pClientVariableHandler->findGroup(sElementPath, true); + } + + PParameterGroup CUIEventFormValues::groupForWrite(CParameterHandler* pClientVariableHandler, const std::string& sElementPath, const std::string& sPropertyName) + { + LibMCAssertNotNull(pClientVariableHandler); + + { + std::lock_guard lockGuard(m_Mutex); + if (findPostedGroup(sElementPath, sPropertyName).get() != nullptr) + m_PostedGroups.erase(sElementPath); + } + + return pClientVariableHandler->findGroup(sElementPath, true); + } + +} diff --git a/Implementation/UI/amc_ui_eventformvalues.hpp b/Implementation/UI/amc_ui_eventformvalues.hpp new file mode 100644 index 00000000..304488ab --- /dev/null +++ b/Implementation/UI/amc_ui_eventformvalues.hpp @@ -0,0 +1,87 @@ +/*++ + +Copyright (C) 2026 Autodesk Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Autodesk Inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL AUTODESK INC. BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + + +#ifndef __AMC_UI_EVENTFORMVALUES +#define __AMC_UI_EVENTFORMVALUES + +#include +#include +#include +#include + +#include "common_chrono.hpp" + +#define AMC_UI_EVENTFORMVALUE_PROPERTY "value" + +namespace AMC { + + class CParameterGroup; + typedef std::shared_ptr PParameterGroup; + + class CParameterHandler; + + class CUIEventFormValues; + typedef std::shared_ptr PUIEventFormValues; + + // The form values posted with one UI event, kept apart from the session's client variables. + // + // A posted value is written to the client variable ".value", which every content + // poll of a sync:value edit overwrites with the bound parameter. A poll between the payload + // write and the handler's GetUIProperty would hand the handler the parameter instead of what + // the user posted. Reads of "value" for a posted entity are therefore served from + // a copy taken when the payload is written; everything else reads the client variables. + class CUIEventFormValues { + private: + AMCCommon::PChrono m_pGlobalChrono; + std::mutex m_Mutex; + std::map m_PostedGroups; + + PParameterGroup findPostedGroup(const std::string& sElementPath, const std::string& sPropertyName); + + public: + CUIEventFormValues(AMCCommon::PChrono pGlobalChrono); + virtual ~CUIEventFormValues(); + + // Records sPostedValue as the event's value of sElementPath. pClientVariableGroup is that + // entity's client variable group; it supplies the value's type, not its value. + void capturePostedValue(const std::string& sElementPath, PParameterGroup pClientVariableGroup, const std::string& sPostedValue); + + // The group to read sPropertyName of sElementPath from during the event. + PParameterGroup groupForRead(CParameterHandler* pClientVariableHandler, const std::string& sElementPath, const std::string& sPropertyName); + + // The group to write sPropertyName of sElementPath to during the event. Writing the value + // of a posted entity replaces the posted value, so later reads see the written one. + PParameterGroup groupForWrite(CParameterHandler* pClientVariableHandler, const std::string& sElementPath, const std::string& sPropertyName); + }; + +} + +#endif //__AMC_UI_EVENTFORMVALUES diff --git a/Implementation/UI/amc_ui_handler.cpp b/Implementation/UI/amc_ui_handler.cpp index ed69a11c..129b0faf 100644 --- a/Implementation/UI/amc_ui_handler.cpp +++ b/Implementation/UI/amc_ui_handler.cpp @@ -838,6 +838,12 @@ CUIHandleEventResponse CUIHandler::handleEvent(const std::string& sEventName, co auto pModuleItem = pPage->findModuleItemByUUID(sEntityUUID); if (pModuleItem.get() != nullptr) { pModuleItem->setEventPayloadValue(sEventName, sEntityUUID, sPayloadValue, pClientVariableHandler); + + // A content poll may resync the client variable before the handler reads it. + if (std::dynamic_pointer_cast(pModuleItem).get() != nullptr) { + std::string sEntityPath = pModuleItem->findElementPathByUUID(sEntityUUID); + pInternalUIEnvironment->capturePostedFormValue(sEntityPath, pClientVariableHandler->findGroup(sEntityPath, true), sPayloadValue); + } } } diff --git a/Implementation/UnitTest/amc_unittests.cpp b/Implementation/UnitTest/amc_unittests.cpp index aa28006e..c6b98bcd 100644 --- a/Implementation/UnitTest/amc_unittests.cpp +++ b/Implementation/UnitTest/amc_unittests.cpp @@ -64,6 +64,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "amc_unittests_scanlab_jumpfilter.hpp" #include "amc_unittests_scanlab_powerramp.hpp" #include "amc_unittests_parquetwriter.hpp" +#include "amc_unittests_eventformvalues.hpp" // LibMCData unit tests #include "amc_unittests_libmcdata_storage.hpp" @@ -122,6 +123,7 @@ CUnitTests::CUnitTests (PUnitTestIO pIO) registerTestGroup(std::make_shared ()); registerTestGroup(std::make_shared ()); registerTestGroup(std::make_shared ()); + registerTestGroup(std::make_shared ()); // LibMCData unit tests registerTestGroup(std::make_shared ()); diff --git a/Implementation/UnitTest/amc_unittests_eventformvalues.hpp b/Implementation/UnitTest/amc_unittests_eventformvalues.hpp new file mode 100644 index 00000000..03641f9c --- /dev/null +++ b/Implementation/UnitTest/amc_unittests_eventformvalues.hpp @@ -0,0 +1,186 @@ +/*++ + +Copyright (C) 2026 Autodesk Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Autodesk Inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL AUTODESK INC. BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef __AMCTEST_UNITTEST_EVENTFORMVALUES +#define __AMCTEST_UNITTEST_EVENTFORMVALUES + + +#include "amc_unittests.hpp" +#include "amc_ui_eventformvalues.hpp" +#include "amc_ui_expression.hpp" +// The test drives a real form edit's poll path, so it opts into the protected UI module headers. +#ifndef __AMCIMPL_UI_MODULE +#define __AMCIMPL_UI_MODULE +#endif +#include "amc_ui_module_contentitem_form.hpp" +#include "amc_statemachinedata.hpp" +#include "amc_parameterhandler.hpp" +#include "amc_parametergroup.hpp" +#include "common_chrono.hpp" + + +namespace AMCUnitTest { + + // A value posted with an event must survive a content poll that resyncs a + // sync:value edit between the payload write and the event handler's read. + class CUnitTestGroup_EventFormValues : public CUnitTestGroup { + public: + + std::string getTestGroupName() override { + return "EventFormValues"; + } + + void registerTests() override { + registerTest("PostedValueSurvivesPoll", "A posted sync:value edit value survives a concurrent content poll", eUnitTestCategory::utMandatoryPass, std::bind(&CUnitTestGroup_EventFormValues::testPostedValueSurvivesPoll, this)); + registerTest("OtherPropertiesAreLive", "Only the posted value is read from the snapshot", eUnitTestCategory::utMandatoryPass, std::bind(&CUnitTestGroup_EventFormValues::testOtherPropertiesAreLive, this)); + registerTest("UnpostedPathsAreLive", "Paths that were not posted read the client variables", eUnitTestCategory::utMandatoryPass, std::bind(&CUnitTestGroup_EventFormValues::testUnpostedPathsAreLive, this)); + registerTest("WriteDropsSnapshot", "Setting the value during the event replaces the posted value", eUnitTestCategory::utMandatoryPass, std::bind(&CUnitTestGroup_EventFormValues::testWriteDropsSnapshot, this)); + } + + void initializeTests() override { + } + + private: + + struct sEditFixture { + AMC::PStateMachineData m_pStateMachineData; + AMC::PParameterGroup m_pMachineGroup; + AMC::PParameterHandler m_pClientVariables; + AMC::PUIModule_ContentFormEdit m_pEdit; + std::string m_sEditPath; + }; + + AMC::CUIExpression createExpression(const std::string& sValue, bool bSync) + { + AMC::CUIExpression expression; + if (bSync) + expression.setSyncValue(sValue); + else + expression.setFixedValue(sValue); + return expression; + } + + // An editable in form "main.start.form", + // with its client variables populated as for a session. + sEditFixture createFixture() + { + auto pChrono = std::make_shared(); + auto pChangeCounter = std::make_shared(); + + sEditFixture fixture; + + auto pMainHandler = std::make_shared("main", pChrono, pChangeCounter); + fixture.m_pMachineGroup = pMainHandler->addGroup("ui", "UI"); + fixture.m_pMachineGroup->addNewIntParameter("laserpower", "Laser power", 700); + + fixture.m_pStateMachineData = std::make_shared(); + fixture.m_pStateMachineData->registerParameterHandler("main", pMainHandler, pChrono); + + fixture.m_pEdit = std::make_shared("laserpower", "main.start.form", + createExpression("Laser Power", false), createExpression("main.ui.laserpower", true), + createExpression("", false), createExpression("", false), "", fixture.m_pStateMachineData); + fixture.m_sEditPath = fixture.m_pEdit->getElementPath(); + + fixture.m_pClientVariables = std::make_shared("client", pChrono, pChangeCounter); + fixture.m_pEdit->populateClientVariables(fixture.m_pClientVariables.get()); + + return fixture; + } + + // What CUIHandler::handleEvent does for each posted form value. + void postValue(sEditFixture& fixture, AMC::CUIEventFormValues& eventFormValues, const std::string& sValue) + { + auto pGroup = fixture.m_pClientVariables->findGroup(fixture.m_sEditPath, true); + pGroup->setParameterValueByName("value", sValue); + eventFormValues.capturePostedValue(fixture.m_sEditPath, pGroup, sValue); + } + + // What a content poll of the form does to the edit. + void poll(sEditFixture& fixture) + { + fixture.m_pEdit->syncClientVariables(fixture.m_pClientVariables.get()); + } + + void testPostedValueSurvivesPoll() + { + auto fixture = createFixture(); + AMC::CUIEventFormValues eventFormValues(std::make_shared()); + + postValue(fixture, eventFormValues, "310"); + poll(fixture); + + assertTrue(fixture.m_pClientVariables->findGroup(fixture.m_sEditPath, true)->getParameterValueByName("value") == "700", "the poll resyncs the shared client variable (the race this group covers)"); + + auto pReadGroup = eventFormValues.groupForRead(fixture.m_pClientVariables.get(), fixture.m_sEditPath, "value"); + assertTrue(pReadGroup->getParameterValueByName("value") == "310", "the handler reads the posted value"); + assertTrue(pReadGroup->getIntParameterValueByName("value") == 310, "the posted value converts like the client variable"); + } + + void testOtherPropertiesAreLive() + { + auto fixture = createFixture(); + AMC::CUIEventFormValues eventFormValues(std::make_shared()); + + postValue(fixture, eventFormValues, "310"); + fixture.m_pClientVariables->findGroup(fixture.m_sEditPath, true)->setParameterValueByName("caption", "Changed"); + + auto pReadGroup = eventFormValues.groupForRead(fixture.m_pClientVariables.get(), fixture.m_sEditPath, "caption"); + assertTrue(pReadGroup->getParameterValueByName("caption") == "Changed"); + } + + void testUnpostedPathsAreLive() + { + auto fixture = createFixture(); + AMC::CUIEventFormValues eventFormValues(std::make_shared()); + + poll(fixture); + auto pReadGroup = eventFormValues.groupForRead(fixture.m_pClientVariables.get(), fixture.m_sEditPath, "value"); + assertTrue(pReadGroup->getParameterValueByName("value") == "700"); + } + + void testWriteDropsSnapshot() + { + auto fixture = createFixture(); + AMC::CUIEventFormValues eventFormValues(std::make_shared()); + + postValue(fixture, eventFormValues, "310"); + + auto pWriteGroup = eventFormValues.groupForWrite(fixture.m_pClientVariables.get(), fixture.m_sEditPath, "value"); + pWriteGroup->setParameterValueByName("value", "500"); + + auto pReadGroup = eventFormValues.groupForRead(fixture.m_pClientVariables.get(), fixture.m_sEditPath, "value"); + assertTrue(pReadGroup->getParameterValueByName("value") == "500", "a value set by the handler is read back"); + } + + }; + +} + +#endif //__AMCTEST_UNITTEST_EVENTFORMVALUES