unit Myc.Test.Core.Lazy; interface uses System.SysUtils, DUnitX.TestFramework, Myc.Signals, // For IMycState, TState, IMycDirty Myc.Lazy, // For IMycLazy Myc.Core.Lazy; // Unit to be tested type [TestFixture] TTestMycCoreLazy = class(TObject) private procedure Helper_ConsumeInitialPop(const ALazy: IMycLazy; InitialExpectedValue: Integer); overload; procedure Helper_ConsumeInitialPop(const ALazy: IMycLazy; const InitialExpectedValue: string); overload; public [Setup] procedure Setup; [TearDown] procedure TearDown; // Tests for TMycNullLazy [Test] procedure TestNullLazy_GetChanged_IsAlwaysNullState; [Test] procedure TestNullLazy_Pop_ReturnsDefaultIntegerAndTrue; [Test] procedure TestNullLazy_Pop_ReturnsDefaultStringAndTrue; [Test] procedure TestNullLazy_Pop_ReturnsDefaultInterfaceAndTrue; // Tests for TMycFuncLazy reflecting "IsSet is true by design after creation" [Test] procedure TestFuncLazy_GetChanged_IsAlwaysTrueAfterCreation; [Test] procedure TestFuncLazy_FirstPop_AlwaysEvaluatesAndResetsChanged; [Test] procedure TestFuncLazy_Pop_WhenProcIsNull_FirstPopReturnsTrueAndValueUnchanged_ResetsChanged; // Tests for behavior after the initial "changed" state is consumed [Test] procedure TestFuncLazy_AfterFirstPop_IfNoSourceChange_GetChangedIsFalse; [Test] procedure TestFuncLazy_AfterFirstPop_IfNoSourceChange_PopReturnsFalse_ValueUndefined; [Test] procedure TestFuncLazy_AfterSourceChange_GetChanged_IsSet; [Test] procedure TestFuncLazy_AfterSourceChange_Pop_ReturnsTrueAndProcResult_ResetsChanged; [Test] procedure TestFuncLazy_Pop_Twice_SourceUnchanged_SecondPopReturnsFalse_ValueUndefined; // This test is now significantly changed to reflect TMycDirty's notification behavior [Test] procedure TestFuncLazy_SourceInteraction_NotifyOnSetSourceDoesNotRetriggerLazy; [Test] procedure TestFuncLazy_Destroy_UnsubscribesFromSource; [Test] procedure TestFuncLazy_SourceIsInitiallySet_PopBehavesCorrectly; end; implementation uses System.Rtti, Myc.Core.Signals; { TTestMycCoreLazy Helper Methods } procedure TTestMycCoreLazy.Helper_ConsumeInitialPop(const ALazy: IMycLazy; InitialExpectedValue: Integer); var tempValue: Integer; popResult: Boolean; begin Assert.IsTrue(ALazy.GetChanged.IsSet, 'Changed.IsSet should be true before consuming initial pop'); popResult := ALazy.Pop(tempValue); Assert.IsTrue(popResult, 'Consuming initial Pop should return true'); Assert.AreEqual(InitialExpectedValue, tempValue, 'Value from initial Pop is unexpected'); Assert.IsFalse(ALazy.GetChanged.IsSet, 'Changed.IsSet should be false after consuming initial pop'); end; procedure TTestMycCoreLazy.Helper_ConsumeInitialPop(const ALazy: IMycLazy; const InitialExpectedValue: string); var tempValue: string; popResult: Boolean; begin Assert.IsTrue(ALazy.GetChanged.IsSet, 'Changed.IsSet should be true before consuming initial pop'); popResult := ALazy.Pop(tempValue); Assert.IsTrue(popResult, 'Consuming initial Pop should return true'); Assert.AreEqual(InitialExpectedValue, tempValue, 'Value from initial Pop is unexpected'); Assert.IsFalse(ALazy.GetChanged.IsSet, 'Changed.IsSet should be false after consuming initial pop'); end; { TTestMycCoreLazy Test Methods } procedure TTestMycCoreLazy.Setup; begin end; procedure TTestMycCoreLazy.TearDown; begin end; // == Tests for TMycNullLazy == procedure TTestMycCoreLazy.TestNullLazy_GetChanged_IsAlwaysNullState; var nullLazy: IMycLazy; changedState: IMycState; begin nullLazy := TMycNullLazy.Create as IMycLazy; Assert.IsNotNull(nullLazy, 'TMycNullLazy instance should not be nil'); changedState := nullLazy.GetChanged; Assert.AreSame(TState.Null, changedState, 'TMycNullLazy.GetChanged should return TState.Null'); Assert.IsTrue(changedState.IsSet, 'TState.Null should always be set'); end; procedure TTestMycCoreLazy.TestNullLazy_Pop_ReturnsDefaultIntegerAndTrue; var nullLazy: IMycLazy; value: Integer; result: Boolean; begin nullLazy := TMycNullLazy.Create as IMycLazy; Assert.IsNotNull(nullLazy, 'TMycNullLazy instance should not be nil'); value := 123; result := nullLazy.Pop(value); Assert.IsTrue(result, 'TMycNullLazy.Pop should return true'); Assert.AreEqual(Default(Integer), value, 'Value should be Default(Integer) after Pop'); end; procedure TTestMycCoreLazy.TestNullLazy_Pop_ReturnsDefaultStringAndTrue; var nullLazy: IMycLazy; value: string; result: Boolean; begin nullLazy := TMycNullLazy.Create as IMycLazy; Assert.IsNotNull(nullLazy, 'TMycNullLazy instance should not be nil'); value := 'test'; result := nullLazy.Pop(value); Assert.IsTrue(result, 'TMycNullLazy.Pop should return true'); Assert.AreEqual(Default(string), value, 'Value should be Default(string) after Pop'); end; procedure TTestMycCoreLazy.TestNullLazy_Pop_ReturnsDefaultInterfaceAndTrue; var nullLazy: IMycLazy; value: IMycState; result: Boolean; begin nullLazy := TMycNullLazy.Create as IMycLazy; Assert.IsNotNull(nullLazy, 'TMycNullLazy instance should not be nil'); value := TState.Null; result := nullLazy.Pop(value); Assert.IsTrue(result, 'TMycNullLazy.Pop should return true'); Assert.IsNull(value, 'Value should be nil for an interface type after Pop from NullLazy'); end; // == Tests for TMycFuncLazy - Initial State by Design == procedure TTestMycCoreLazy.TestFuncLazy_GetChanged_IsAlwaysTrueAfterCreation; var funcLazy: IMycLazy; changedState: IMycState; sourceDirty: IMycDirty; begin sourceDirty := TDirty.Construct; sourceDirty.Reset; funcLazy := TMycFuncLazy.Create(sourceDirty.State, function: Integer begin Result := 10; end); Assert.IsNotNull(funcLazy, 'TMycFuncLazy instance should not be nil'); changedState := funcLazy.GetChanged; Assert.IsNotNull(changedState, 'funcLazy.GetChanged should return a valid IMycState'); Assert.IsTrue(changedState.IsSet, 'By design, funcLazy.GetChanged.IsSet should be true immediately after creation'); end; procedure TTestMycCoreLazy.TestFuncLazy_FirstPop_AlwaysEvaluatesAndResetsChanged; var funcLazy: IMycLazy; value: Integer; result: Boolean; sourceDirty: IMycDirty; procExecuted: Boolean; expectedValue: Integer; begin sourceDirty := TDirty.Construct; sourceDirty.Reset; procExecuted := False; expectedValue := 50; funcLazy := TMycFuncLazy.Create(sourceDirty.State, function: Integer begin procExecuted := True; Result := expectedValue; end); Assert.IsNotNull(funcLazy, 'TMycFuncLazy instance should not be nil'); Assert.IsTrue(funcLazy.GetChanged.IsSet, 'Changed.IsSet should be true before the first Pop by design'); value := 0; result := funcLazy.Pop(value); Assert.IsTrue(result, 'The first Pop should return true by design, indicating evaluation'); Assert.IsTrue(procExecuted, 'FProc should have been executed on the first Pop'); Assert.AreEqual(expectedValue, value, 'Value should be the result from FProc after the first Pop'); Assert.IsFalse(funcLazy.GetChanged.IsSet, 'Changed.IsSet should be false after the first Pop, as Pop resets it'); end; procedure TTestMycCoreLazy.TestFuncLazy_Pop_WhenProcIsNull_FirstPopReturnsTrueAndValueUnchanged_ResetsChanged; var funcLazy: IMycLazy; value: Integer; preCallValue: Integer; result: Boolean; sourceDirty: IMycDirty; begin sourceDirty := TDirty.Construct; sourceDirty.Reset; funcLazy := TMycFuncLazy.Create(sourceDirty.State, nil); Assert.IsNotNull(funcLazy, 'TMycFuncLazy instance should not be nil'); Assert.IsTrue(funcLazy.GetChanged.IsSet, 'Changed state should be true before Pop by design'); preCallValue := 123; value := preCallValue; result := funcLazy.Pop(value); Assert.IsTrue(result, 'First Pop should return true by design, even if FProc is nil'); Assert.AreEqual(preCallValue, value, 'Value should be unchanged as FProc was nil and current Pop implementation does not assign Default(T)'); Assert.IsFalse(funcLazy.GetChanged.IsSet, 'Changed state should be false after Pop'); end; // == Tests for TMycFuncLazy - Behavior After Initial Pop == procedure TTestMycCoreLazy.TestFuncLazy_AfterFirstPop_IfNoSourceChange_GetChangedIsFalse; var funcLazy: IMycLazy; sourceDirty: IMycDirty; initialProcValue: Integer; begin sourceDirty := TDirty.Construct; sourceDirty.Reset; initialProcValue := 33; funcLazy := TMycFuncLazy.Create(sourceDirty.State, function: Integer begin Result := initialProcValue; end); Helper_ConsumeInitialPop(funcLazy, initialProcValue); Assert.IsFalse(funcLazy.GetChanged.IsSet, 'After initial Pop and no source change, GetChanged.IsSet should be false'); end; procedure TTestMycCoreLazy.TestFuncLazy_AfterFirstPop_IfNoSourceChange_PopReturnsFalse_ValueUndefined; var funcLazy: IMycLazy; value: Integer; result: Boolean; sourceDirty: IMycDirty; initialProcValue: Integer; procExecutedCount: Integer; begin sourceDirty := TDirty.Construct; sourceDirty.Reset; initialProcValue := 44; procExecutedCount := 0; funcLazy := TMycFuncLazy.Create(sourceDirty.State, function: Integer begin Inc(procExecutedCount); Result := initialProcValue; end); Helper_ConsumeInitialPop(funcLazy, initialProcValue); Assert.AreEqual(1, procExecutedCount, 'Proc should have executed once for initial pop'); result := funcLazy.Pop(value); Assert.IsFalse(result, 'Second Pop (after initial, no source change) should return false'); Assert.AreEqual(1, procExecutedCount, 'Proc should not have executed again'); end; procedure TTestMycCoreLazy.TestFuncLazy_AfterSourceChange_GetChanged_IsSet; var funcLazy: IMycLazy; changedState: IMycState; sourceDirty: IMycDirty; initialProcValue: Integer; begin sourceDirty := TDirty.Construct; sourceDirty.Reset; initialProcValue := 20; funcLazy := TMycFuncLazy.Create(sourceDirty.State, function: Integer begin Result := initialProcValue; end); Helper_ConsumeInitialPop(funcLazy, initialProcValue); sourceDirty.Notify; changedState := funcLazy.GetChanged; Assert.IsTrue(changedState.IsSet, 'After source change (post-initial pop), funcLazy.GetChanged.IsSet should be true'); end; procedure TTestMycCoreLazy.TestFuncLazy_AfterSourceChange_Pop_ReturnsTrueAndProcResult_ResetsChanged; var funcLazy: IMycLazy; value: Integer; result: Boolean; sourceDirty: IMycDirty; procCallCount: Integer; currentExpectedValue: Integer; begin sourceDirty := TDirty.Construct; sourceDirty.Reset; procCallCount := 0; funcLazy := TMycFuncLazy.Create(sourceDirty.State, function: Integer begin Inc(procCallCount); if procCallCount = 1 then Result := 30 else Result := 300 + procCallCount; end); currentExpectedValue := 30; Helper_ConsumeInitialPop(funcLazy, currentExpectedValue); Assert.AreEqual(1, procCallCount, 'Proc executed for initial Pop'); sourceDirty.Notify; Assert.IsTrue(funcLazy.GetChanged.IsSet, 'Changed state should be true after source notification'); value := 0; result := funcLazy.Pop(value); currentExpectedValue := 300 + 2; Assert.IsTrue(result, 'Pop should return true after source changed'); Assert.AreEqual(2, procCallCount, 'Proc should have been executed again'); Assert.AreEqual(currentExpectedValue, value, 'Value should be the new result of FProc'); Assert.IsFalse(funcLazy.GetChanged.IsSet, 'Changed state should be false after Pop'); end; procedure TTestMycCoreLazy.TestFuncLazy_Pop_Twice_SourceUnchanged_SecondPopReturnsFalse_ValueUndefined; var funcLazy: IMycLazy; value: Integer; resultPop1, resultPop2: Boolean; sourceDirty: IMycDirty; procCallCount: Integer; begin sourceDirty := TDirty.Construct; sourceDirty.Reset; procCallCount := 0; funcLazy := TMycFuncLazy.Create(sourceDirty.State, function: Integer begin Inc(procCallCount); Result := 40; end); resultPop1 := funcLazy.Pop(value); Assert.IsTrue(resultPop1, 'First Pop should return true by design'); Assert.AreEqual(40, value, 'Value from first Pop'); Assert.AreEqual(1, procCallCount, 'Proc called for first Pop'); Assert.IsFalse(funcLazy.GetChanged.IsSet, 'Changed state should be false after first Pop'); resultPop2 := funcLazy.Pop(value); Assert.IsFalse(resultPop2, 'Second Pop should return false as state was reset and not changed again by source'); Assert.AreEqual(1, procCallCount, 'Proc should not be called for second Pop if no source change'); end; // TestFuncLazy_SourceChanges_Pop_SourceChangesAgain_PopAgain has been RENAMED and RESTRUCTURED // to TestFuncLazy_SourceInteraction_NotifyOnSetSourceDoesNotRetriggerLazy procedure TTestMycCoreLazy.TestFuncLazy_SourceInteraction_NotifyOnSetSourceDoesNotRetriggerLazy; var funcLazy: IMycLazy; value: Integer; result: Boolean; sourceDirty: IMycDirty; procCallCount: Integer; initialValue, firstSourceChangeValue: Integer; begin sourceDirty := TDirty.Construct; sourceDirty.Reset; // sourceDirty.IsSet is FALSE procCallCount := 0; initialValue := 51; firstSourceChangeValue := 52; funcLazy := TMycFuncLazy.Create(sourceDirty.State, function: Integer begin Inc(procCallCount); if procCallCount = 1 then Result := initialValue // For initial pop else if procCallCount = 2 then Result := firstSourceChangeValue // For pop after first effective source change else Result := 999; // Should not be reached in this specific test logic end); // 1. Initial Pop (consumes "by design" changed state) Assert.IsTrue(funcLazy.GetChanged.IsSet, 'Changed state should be true before first Pop (by design)'); result := funcLazy.Pop(value); // procCallCount becomes 1 Assert.IsTrue(result, 'First Pop should return true'); Assert.AreEqual(initialValue, value, 'Value from first Pop'); Assert.AreEqual(1, procCallCount, 'Proc called for initial Pop'); Assert.IsFalse(funcLazy.GetChanged.IsSet, 'Changed state should be false after first Pop'); // 2. First effective source change (sourceDirty: false -> true) sourceDirty.Notify; // sourceDirty.IsSet becomes TRUE, notifies funcLazy.FChanged, funcLazy.GetChanged.IsSet becomes TRUE Assert.IsTrue(sourceDirty.State.IsSet, 'sourceDirty should be set after first Notify'); Assert.IsTrue(funcLazy.GetChanged.IsSet, 'Changed state should be true after first effective source notification'); result := funcLazy.Pop(value); // procCallCount becomes 2 Assert.IsTrue(result, 'Pop after first effective source change should return true'); Assert.AreEqual(firstSourceChangeValue, value, 'Value from Pop after first effective source change'); Assert.AreEqual(2, procCallCount, 'Proc called again'); Assert.IsFalse(funcLazy.GetChanged.IsSet, 'Changed state should be false after second Pop'); // 3. Notify sourceDirty again (sourceDirty is already TRUE) Assert.IsTrue(sourceDirty.State.IsSet, 'sourceDirty is still set before second Notify attempt'); sourceDirty.Notify; // Since sourceDirty is already set, this does NOT notify funcLazy.FChanged. // funcLazy.GetChanged.IsSet remains FALSE. Assert.IsFalse(funcLazy.GetChanged.IsSet, 'Changed state should REMAIN false after Notify on an already-set source'); // 4. Attempt to Pop again result := funcLazy.Pop(value); // procCallCount should remain 2 Assert.IsFalse(result, 'Pop after Notify on an already-set source should return false'); Assert.AreEqual(2, procCallCount, 'Proc should NOT have been called again'); // Value of 'value' is undefined here and not checked. end; procedure TTestMycCoreLazy.TestFuncLazy_Destroy_UnsubscribesFromSource; var funcLazyObj: TMycFuncLazy; sourceDirty: IMycDirty; tempVal: Integer; begin sourceDirty := TDirty.Construct; sourceDirty.Reset; funcLazyObj := TMycFuncLazy.Create(sourceDirty.State, function: Integer begin Result := 1; end); Assert.IsTrue(funcLazyObj.Pop(tempVal), 'Initial Pop should succeed'); funcLazyObj.Destroy; funcLazyObj := nil; Assert.WillNotRaise( procedure begin sourceDirty.Notify; end, nil, 'Destroy test assumes TMycSubscription.Unsubscribe works. Verified by no crash on source notify post-destroy.'); sourceDirty := nil; end; procedure TTestMycCoreLazy.TestFuncLazy_SourceIsInitiallySet_PopBehavesCorrectly; var funcLazy: IMycLazy; value: Integer; result: Boolean; sourceDirty: IMycDirty; expectedValue: Integer; procExecuted: Boolean; begin sourceDirty := TDirty.Construct; Assert.IsTrue(sourceDirty.State.IsSet, 'SourceDirty should be initially set for this test scenario'); expectedValue := 70; procExecuted := False; funcLazy := TMycFuncLazy.Create(sourceDirty.State, function: Integer begin procExecuted := True; Result := expectedValue; end); Assert.IsNotNull(funcLazy, 'TMycFuncLazy instance should not be nil'); Assert.IsTrue(funcLazy.GetChanged.IsSet, 'funcLazy.GetChanged.IsSet should be true after creation (by design)'); value := 0; result := funcLazy.Pop(value); Assert.IsTrue(result, 'First Pop should return true'); Assert.IsTrue(procExecuted, 'FProc should have been executed on first Pop'); Assert.AreEqual(expectedValue, value, 'Value should be the result of FProc'); Assert.IsFalse(funcLazy.GetChanged.IsSet, 'Changed state should be false after the first Pop'); end; initialization TDUnitX.RegisterTestFixture(TTestMycCoreLazy); end.