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