530 lines
20 KiB
ObjectPascal
530 lines
20 KiB
ObjectPascal
unit TestSignals_Dirty;
|
|
|
|
interface
|
|
|
|
uses
|
|
DUnitX.TestFramework,
|
|
System.SysUtils,
|
|
Myc.Signals; // Unit under test
|
|
|
|
type
|
|
// Helper class to mock a subscriber (reuse or redefine if not in common unit)
|
|
TMockSubscriber = class(TInterfacedObject, IMycSubscriber)
|
|
public
|
|
NotifyCount: Integer;
|
|
NotifyShouldReturn: Boolean;
|
|
WasCalled: Boolean;
|
|
LastReturnedValueFromSource: Boolean; // To store what the source's Notify returned
|
|
|
|
constructor Create(AShouldReturn: Boolean = True);
|
|
function Notify: Boolean; // IMycSubscriber implementation.
|
|
end;
|
|
|
|
[TestFixture]
|
|
TTestMycDirtyFlag = class(TObject)
|
|
private
|
|
// Helper to create a dirty flag and optionally reset it for a clean initial state
|
|
function CreateAndPrepareDirtyFlag(StartDirty: Boolean = True): IMycDirty;
|
|
public
|
|
[Setup]
|
|
procedure Setup;
|
|
[TearDown]
|
|
procedure TearDown;
|
|
|
|
// Category 1: Basic State, Notify (as Subscriber method), and Reset
|
|
[Test]
|
|
procedure TestCreate_InitialStateIsDirty;
|
|
[Test]
|
|
procedure TestReset_FromDirtyState_BecomesCleanAndReturnsTrue;
|
|
[Test]
|
|
procedure TestReset_FromCleanState_StaysCleanAndReturnsFalse;
|
|
[Test]
|
|
procedure TestNotify_OnCleanFlag_SetsDirtyAndReturnsTrue;
|
|
[Test]
|
|
procedure TestNotify_OnDirtyFlag_StaysDirtyAndReturnsFalse;
|
|
|
|
// Category 2: Subscriber Notifications
|
|
[Test]
|
|
procedure TestSubscribe_ToAlreadyDirtyFlag_NotifiesSubscriberImmediately;
|
|
[Test]
|
|
procedure TestSubscribe_ToCleanFlag_DoesNotNotifySubscriberImmediately;
|
|
[Test]
|
|
procedure TestSubscriber_Notified_WhenFlagTransitionsToDirty;
|
|
[Test]
|
|
procedure TestSubscriber_NotNotified_WhenSettingAlreadyDirtyFlagViaNotify;
|
|
[Test]
|
|
procedure TestSubscriber_NotNotified_ByResetAction;
|
|
[Test]
|
|
procedure TestMultipleSubscribers_Notified_WhenFlagTransitionsToDirty;
|
|
[Test]
|
|
procedure TestUnsubscribe_SubscriberNotNotified;
|
|
|
|
// Category 3: Chaining Dirty Flags
|
|
[Test]
|
|
procedure TestChain_A_Notifies_B_SimplePropagation;
|
|
[Test]
|
|
procedure TestChain_A_Notifies_B_Notifies_C_SimplePropagation;
|
|
|
|
// Category 4: Chaining with Resets and Re-triggering (Consistency)
|
|
[Test]
|
|
procedure TestChain_A_B_ResetB_RetriggerA_BStaysCleanIfANotChanged;
|
|
[Test]
|
|
procedure TestChain_A_B_ResetA_ThenTriggerA_FullPropagationToB;
|
|
[Test]
|
|
procedure TestChain_A_B_C_IntermediateBReset_RetriggerA_PropagatesToC;
|
|
[Test]
|
|
procedure TestChain_A_B_C_AllReset_RetriggerA_FullPropagation;
|
|
[Test]
|
|
procedure TestChain_A_B_C_TriggerA_ResetA_TriggerA_EnsureCNotifiedCorrectly;
|
|
|
|
end;
|
|
|
|
implementation
|
|
|
|
{ TMockSubscriber }
|
|
|
|
constructor TMockSubscriber.Create(AShouldReturn: Boolean = True);
|
|
begin
|
|
inherited Create;
|
|
NotifyCount := 0;
|
|
NotifyShouldReturn := AShouldReturn;
|
|
WasCalled := False;
|
|
LastReturnedValueFromSource := False; // Default
|
|
end;
|
|
|
|
function TMockSubscriber.Notify: Boolean;
|
|
begin
|
|
Inc(NotifyCount);
|
|
WasCalled := True;
|
|
// This return value is what this subscriber tells the source signal.
|
|
// For testing, we usually want it to return false to signify it doesn't need more from this one event.
|
|
Result := NotifyShouldReturn;
|
|
end;
|
|
|
|
{ TTestMycDirtyFlag }
|
|
|
|
function TTestMycDirtyFlag.CreateAndPrepareDirtyFlag(StartDirty: Boolean = True): IMycDirty;
|
|
begin
|
|
Result := TDirty.Construct; // Initially dirty
|
|
if not StartDirty then
|
|
Result.Reset; // Reset to make it clean
|
|
Assert.AreEqual(StartDirty, Result.State.IsSet, 'CreateAndPrepareDirtyFlag initial state incorrect.');
|
|
end;
|
|
|
|
procedure TTestMycDirtyFlag.Setup;
|
|
begin
|
|
// Per-test setup
|
|
end;
|
|
|
|
procedure TTestMycDirtyFlag.TearDown;
|
|
begin
|
|
// Per-test teardown
|
|
end;
|
|
|
|
// Category 1: Basic State, Notify (as Subscriber method), and Reset
|
|
|
|
procedure TTestMycDirtyFlag.TestCreate_InitialStateIsDirty;
|
|
var
|
|
dirtyFlag: IMycDirty;
|
|
begin
|
|
dirtyFlag := TDirty.Construct;
|
|
Assert.IsTrue(dirtyFlag.State.IsSet, 'Newly created dirty flag should be IsSet (dirty).');
|
|
end;
|
|
|
|
procedure TTestMycDirtyFlag.TestReset_FromDirtyState_BecomesCleanAndReturnsTrue;
|
|
var
|
|
dirtyFlag: IMycDirty;
|
|
resetResult: Boolean;
|
|
begin
|
|
dirtyFlag := CreateAndPrepareDirtyFlag(True); // Start dirty
|
|
|
|
resetResult := dirtyFlag.Reset;
|
|
|
|
Assert.IsFalse(dirtyFlag.State.IsSet, 'Flag should be clean (not IsSet) after Reset.');
|
|
Assert.IsTrue(resetResult, 'Reset() should return True when resetting a dirty flag.');
|
|
end;
|
|
|
|
procedure TTestMycDirtyFlag.TestReset_FromCleanState_StaysCleanAndReturnsFalse;
|
|
var
|
|
dirtyFlag: IMycDirty;
|
|
resetResult: Boolean;
|
|
begin
|
|
dirtyFlag := CreateAndPrepareDirtyFlag(False); // Start clean
|
|
|
|
resetResult := dirtyFlag.Reset;
|
|
|
|
Assert.IsFalse(dirtyFlag.State.IsSet, 'Flag should remain clean (not IsSet) after Reset on clean flag.');
|
|
Assert.IsFalse(resetResult, 'Reset() should return False when resetting an already clean flag.');
|
|
end;
|
|
|
|
procedure TTestMycDirtyFlag.TestNotify_OnCleanFlag_SetsDirtyAndReturnsTrue;
|
|
var
|
|
dirtyFlag: IMycDirty;
|
|
notifyResult: Boolean;
|
|
begin
|
|
dirtyFlag := CreateAndPrepareDirtyFlag(False); // Start clean
|
|
|
|
notifyResult := dirtyFlag.Notify; // Call IMycSubscriber.Notify to make it dirty
|
|
|
|
Assert.IsTrue(dirtyFlag.State.IsSet, 'Flag should be dirty (IsSet) after Notify on clean flag.');
|
|
Assert.IsTrue(notifyResult, 'Notify() should return True indicating a state change from clean to dirty.');
|
|
end;
|
|
|
|
procedure TTestMycDirtyFlag.TestNotify_OnDirtyFlag_StaysDirtyAndReturnsFalse;
|
|
var
|
|
dirtyFlag: IMycDirty;
|
|
notifyResult: Boolean;
|
|
begin
|
|
dirtyFlag := CreateAndPrepareDirtyFlag(True); // Start dirty
|
|
|
|
notifyResult := dirtyFlag.Notify; // Call IMycSubscriber.Notify again
|
|
|
|
Assert.IsTrue(dirtyFlag.State.IsSet, 'Flag should remain dirty (IsSet).');
|
|
Assert.IsFalse(notifyResult, 'Notify() should return False as flag was already dirty (no state change to dirty).');
|
|
end;
|
|
|
|
// Category 2: Subscriber Notifications
|
|
|
|
procedure TTestMycDirtyFlag.TestSubscribe_ToAlreadyDirtyFlag_NotifiesSubscriberImmediately;
|
|
var
|
|
dirtyFlag: IMycDirty;
|
|
subscriber: TMockSubscriber;
|
|
subscription: TState.TSubscription;
|
|
begin
|
|
dirtyFlag := CreateAndPrepareDirtyFlag(True); // Start dirty
|
|
subscriber := TMockSubscriber.Create;
|
|
|
|
subscription := dirtyFlag.State.Subscribe(subscriber);
|
|
|
|
Assert.AreEqual(1, subscriber.NotifyCount, 'Subscriber should be notified immediately when subscribing to an already dirty flag.');
|
|
Assert.IsTrue(subscriber.WasCalled, 'Subscriber WasCalled should be true.');
|
|
end;
|
|
|
|
procedure TTestMycDirtyFlag.TestSubscribe_ToCleanFlag_DoesNotNotifySubscriberImmediately;
|
|
var
|
|
dirtyFlag: IMycDirty;
|
|
subscriber: TMockSubscriber;
|
|
subscription: TState.TSubscription;
|
|
begin
|
|
dirtyFlag := CreateAndPrepareDirtyFlag(False); // Start clean
|
|
subscriber := TMockSubscriber.Create;
|
|
|
|
subscription := dirtyFlag.State.Subscribe(subscriber);
|
|
|
|
Assert.AreEqual(0, subscriber.NotifyCount, 'Subscriber should NOT be notified immediately when subscribing to a clean flag.');
|
|
Assert.IsFalse(subscriber.WasCalled, 'Subscriber WasCalled should be false.');
|
|
end;
|
|
|
|
procedure TTestMycDirtyFlag.TestSubscriber_Notified_WhenFlagTransitionsToDirty;
|
|
var
|
|
dirtyFlag: IMycDirty;
|
|
subscriber: TMockSubscriber;
|
|
subscription: TState.TSubscription;
|
|
begin
|
|
dirtyFlag := CreateAndPrepareDirtyFlag(False); // Start clean
|
|
subscriber := TMockSubscriber.Create;
|
|
subscription := dirtyFlag.State.Subscribe(subscriber);
|
|
|
|
Assert.AreEqual(0, subscriber.NotifyCount, 'Subscriber initially not notified.');
|
|
|
|
dirtyFlag.Notify; // Make the flag dirty
|
|
|
|
Assert.AreEqual(1, subscriber.NotifyCount, 'Subscriber should be notified once flag transitions to dirty.');
|
|
end;
|
|
|
|
procedure TTestMycDirtyFlag.TestSubscriber_NotNotified_WhenSettingAlreadyDirtyFlagViaNotify;
|
|
var
|
|
dirtyFlag: IMycDirty;
|
|
subscriber: TMockSubscriber;
|
|
subscription: TState.TSubscription;
|
|
begin
|
|
dirtyFlag := CreateAndPrepareDirtyFlag(True); // Start dirty
|
|
subscriber := TMockSubscriber.Create;
|
|
subscription := dirtyFlag.State.Subscribe(subscriber); // Gets initial notification
|
|
|
|
Assert.AreEqual(1, subscriber.NotifyCount, 'Subscriber notified on subscribe to dirty flag.');
|
|
|
|
dirtyFlag.Notify; // Notify again, flag was already dirty
|
|
|
|
Assert.AreEqual(1, subscriber.NotifyCount, 'Subscriber should NOT be notified again if flag was already dirty.');
|
|
end;
|
|
|
|
procedure TTestMycDirtyFlag.TestSubscriber_NotNotified_ByResetAction;
|
|
var
|
|
dirtyFlag: IMycDirty;
|
|
subscriber: TMockSubscriber;
|
|
subscription: TState.TSubscription;
|
|
begin
|
|
dirtyFlag := CreateAndPrepareDirtyFlag(True); // Start dirty
|
|
subscriber := TMockSubscriber.Create;
|
|
subscription := dirtyFlag.State.Subscribe(subscriber); // Gets initial notification
|
|
|
|
Assert.AreEqual(1, subscriber.NotifyCount, 'Subscriber notified on subscribe.');
|
|
dirtyFlag.Reset; // Reset the flag
|
|
Assert.IsFalse(dirtyFlag.State.IsSet, 'Flag is now clean.');
|
|
|
|
// Current TMycDirty.Reset does not notify subscribers.
|
|
Assert.AreEqual(1, subscriber.NotifyCount, 'Subscriber should NOT be notified by Reset action.');
|
|
end;
|
|
|
|
procedure TTestMycDirtyFlag.TestMultipleSubscribers_Notified_WhenFlagTransitionsToDirty;
|
|
var
|
|
dirtyFlag: IMycDirty;
|
|
sub1, sub2: TMockSubscriber;
|
|
subscription1, subscription2: TState.TSubscription;
|
|
begin
|
|
dirtyFlag := CreateAndPrepareDirtyFlag(False); // Start clean
|
|
sub1 := TMockSubscriber.Create;
|
|
sub2 := TMockSubscriber.Create;
|
|
subscription1 := dirtyFlag.State.Subscribe(sub1);
|
|
subscription2 := dirtyFlag.State.Subscribe(sub2);
|
|
|
|
dirtyFlag.Notify; // Make flag dirty
|
|
|
|
Assert.AreEqual(1, sub1.NotifyCount, 'Subscriber 1 should be notified.');
|
|
Assert.AreEqual(1, sub2.NotifyCount, 'Subscriber 2 should be notified.');
|
|
end;
|
|
|
|
procedure TTestMycDirtyFlag.TestUnsubscribe_SubscriberNotNotified;
|
|
var
|
|
dirtyFlag: IMycDirty;
|
|
subscriber: TMockSubscriber;
|
|
subscription: TState.TSubscription;
|
|
begin
|
|
dirtyFlag := CreateAndPrepareDirtyFlag(False); // Start clean
|
|
subscriber := TMockSubscriber.Create;
|
|
subscription := dirtyFlag.State.Subscribe(subscriber);
|
|
|
|
subscription.Unsubscribe; // Explicitly unsubscribe
|
|
|
|
dirtyFlag.Notify; // Make flag dirty
|
|
|
|
Assert.AreEqual(0, subscriber.NotifyCount, 'Unsubscribed subscriber should not be notified.');
|
|
end;
|
|
|
|
// Category 3: Chaining Dirty Flags
|
|
// In these tests, FlagX.Notify means calling the IMycSubscriber.Notify method on FlagX.
|
|
// This makes FlagX dirty and potentially notifies its subscribers (like FlagY).
|
|
|
|
procedure TTestMycDirtyFlag.TestChain_A_Notifies_B_SimplePropagation;
|
|
var
|
|
flagA, flagB: IMycDirty;
|
|
subToB: TMockSubscriber;
|
|
subscriptionA_B, subscriptionMock_B: TState.TSubscription;
|
|
begin
|
|
flagA := CreateAndPrepareDirtyFlag(False); // A starts clean
|
|
flagB := CreateAndPrepareDirtyFlag(False); // B starts clean
|
|
subToB := TMockSubscriber.Create;
|
|
|
|
subscriptionMock_B := flagB.State.Subscribe(subToB);
|
|
subscriptionA_B := flagA.State.Subscribe(flagB); // B subscribes to A's state changes (B's Notify will be called)
|
|
|
|
flagA.Notify; // Make A dirty. This should trigger B.Notify
|
|
|
|
Assert.IsTrue(flagA.State.IsSet, 'Flag A should be dirty.');
|
|
Assert.IsTrue(flagB.State.IsSet, 'Flag B should become dirty due to A.');
|
|
Assert.AreEqual(1, subToB.NotifyCount, 'Subscriber to B should be notified once.');
|
|
end;
|
|
|
|
procedure TTestMycDirtyFlag.TestChain_A_Notifies_B_Notifies_C_SimplePropagation;
|
|
var
|
|
flagA, flagB, flagC: IMycDirty;
|
|
subToC: TMockSubscriber;
|
|
subscriptionB_A, subscriptionC_B, subscriptionMock_C: TState.TSubscription;
|
|
begin
|
|
flagA := CreateAndPrepareDirtyFlag(False);
|
|
flagB := CreateAndPrepareDirtyFlag(False);
|
|
flagC := CreateAndPrepareDirtyFlag(False);
|
|
subToC := TMockSubscriber.Create;
|
|
|
|
subscriptionMock_C := flagC.State.Subscribe(subToC);
|
|
subscriptionC_B := flagB.State.Subscribe(flagC); // C subscribes to B
|
|
subscriptionB_A := flagA.State.Subscribe(flagB); // B subscribes to A
|
|
|
|
flagA.Notify; // Make A dirty
|
|
|
|
Assert.IsTrue(flagA.State.IsSet, 'Flag A should be dirty in cascade.');
|
|
Assert.IsTrue(flagB.State.IsSet, 'Flag B should be dirty in cascade.');
|
|
Assert.IsTrue(flagC.State.IsSet, 'Flag C should be dirty in cascade.');
|
|
Assert.AreEqual(1, subToC.NotifyCount, 'Subscriber to C should be notified once in cascade.');
|
|
end;
|
|
|
|
// Category 4: Chaining with Resets and Re-triggering (Consistency)
|
|
|
|
procedure TTestMycDirtyFlag.TestChain_A_B_ResetB_RetriggerA_BStaysCleanIfANotChanged;
|
|
var
|
|
flagA, flagB: IMycDirty;
|
|
subToB: TMockSubscriber;
|
|
subscriptionA_B, subscriptionMock_B: TState.TSubscription;
|
|
begin
|
|
flagA := CreateAndPrepareDirtyFlag(False);
|
|
flagB := CreateAndPrepareDirtyFlag(False);
|
|
subToB := TMockSubscriber.Create;
|
|
subscriptionMock_B := flagB.State.Subscribe(subToB);
|
|
subscriptionA_B := flagA.State.Subscribe(flagB);
|
|
|
|
flagA.Notify; // A dirty -> B dirty. subToB notified.
|
|
Assert.IsTrue(flagB.State.IsSet, 'Flag B should be dirty initially.');
|
|
Assert.AreEqual(1, subToB.NotifyCount, 'Subscriber to B initially notified.');
|
|
|
|
flagB.Reset; // B becomes clean. A is still dirty.
|
|
Assert.IsFalse(flagB.State.IsSet, 'Flag B should be clean after reset.');
|
|
Assert.AreEqual(1, subToB.NotifyCount, 'Subscriber to B not notified by B.Reset.');
|
|
|
|
// Notify A again. A was already dirty. So A.Notify() returns false, A does not call ProtectedNotifyOwnSubscribers.
|
|
// Thus, B.Notify() is NOT called. B should remain clean.
|
|
flagA.Notify;
|
|
|
|
Assert.IsTrue(flagA.State.IsSet, 'Flag A remains dirty.');
|
|
Assert.IsFalse(flagB.State.IsSet, 'Flag B should remain clean if A did not transition to dirty.');
|
|
Assert.AreEqual(1, subToB.NotifyCount, 'Subscriber to B should not be notified again.');
|
|
end;
|
|
|
|
procedure TTestMycDirtyFlag.TestChain_A_B_ResetA_ThenTriggerA_FullPropagationToB;
|
|
var
|
|
flagA, flagB: IMycDirty;
|
|
subToB: TMockSubscriber;
|
|
subscriptionA_B, subscriptionMock_B: TState.TSubscription;
|
|
begin
|
|
flagA := CreateAndPrepareDirtyFlag(False);
|
|
flagB := CreateAndPrepareDirtyFlag(False);
|
|
subToB := TMockSubscriber.Create;
|
|
subscriptionMock_B := flagB.State.Subscribe(subToB);
|
|
subscriptionA_B := flagA.State.Subscribe(flagB);
|
|
|
|
flagA.Notify; // A dirty -> B dirty. subToB notified.
|
|
Assert.IsTrue(flagB.State.IsSet, 'Flag B should be dirty after A triggers.');
|
|
Assert.AreEqual(1, subToB.NotifyCount, 'subToB notified once.');
|
|
|
|
flagA.Reset; // A becomes clean. B is still dirty (state change of A to clean doesn't propagate as a "dirty" signal to B).
|
|
Assert.IsFalse(flagA.State.IsSet, 'Flag A is clean after reset.');
|
|
Assert.IsTrue(flagB.State.IsSet, 'Flag B remains dirty.'); // B's state doesn't change because A becoming clean doesn't call B.Notify
|
|
|
|
flagA.Notify; // A (was clean) becomes dirty again. A.Notify() returns true. A notifies B.
|
|
// B.Notify() is called. B was dirty. B.Notify() sets FFlag to true (no change), returns false.
|
|
// So B does NOT notify subToB again.
|
|
|
|
Assert.IsTrue(flagA.State.IsSet, 'Flag A is dirty again.');
|
|
Assert.IsTrue(flagB.State.IsSet, 'Flag B is dirty (was already, and A.Notify called B.Notify which set it dirty again).');
|
|
Assert.AreEqual(1, subToB.NotifyCount, 'subToB should NOT be notified again as B did not transition from clean to dirty.');
|
|
end;
|
|
|
|
procedure TTestMycDirtyFlag.TestChain_A_B_C_IntermediateBReset_RetriggerA_PropagatesToC;
|
|
var
|
|
flagA, flagB, flagC: IMycDirty;
|
|
subToC: TMockSubscriber;
|
|
subscriptionB_A, subscriptionC_B, subscriptionMock_C: TState.TSubscription;
|
|
begin
|
|
flagA := CreateAndPrepareDirtyFlag(False);
|
|
flagB := CreateAndPrepareDirtyFlag(False);
|
|
flagC := CreateAndPrepareDirtyFlag(False);
|
|
subToC := TMockSubscriber.Create;
|
|
|
|
subscriptionMock_C := flagC.State.Subscribe(subToC);
|
|
subscriptionC_B := flagB.State.Subscribe(flagC);
|
|
subscriptionB_A := flagA.State.Subscribe(flagB);
|
|
|
|
// Initial full propagation
|
|
flagA.Notify; // A dirty -> B dirty -> C dirty. subToC notified.
|
|
Assert.IsTrue(flagC.State.IsSet, 'Flag C should be dirty initially.');
|
|
Assert.AreEqual(1, subToC.NotifyCount, 'subToC initial count.');
|
|
|
|
// Intermediate reset
|
|
flagB.Reset; // B becomes clean. A is dirty, C is dirty.
|
|
Assert.IsFalse(flagB.State.IsSet, 'Flag B is clean.');
|
|
|
|
// Reset and re-trigger A
|
|
flagA.Reset; // A becomes clean.
|
|
Assert.IsFalse(flagA.State.IsSet, 'Flag A is clean.');
|
|
|
|
flagA.Notify; // A (was clean) -> A dirty. A notifies B.
|
|
// B.Notify() called. B was clean -> B dirty. B notifies C.
|
|
// C.Notify() called. C was dirty -> C set dirty (no change), C.Notify() returns false.
|
|
// So subToC is NOT notified again.
|
|
|
|
Assert.IsTrue(flagA.State.IsSet, 'Flag A is dirty again.');
|
|
Assert.IsTrue(flagB.State.IsSet, 'Flag B becomes dirty again.');
|
|
Assert.IsTrue(flagC.State.IsSet, 'Flag C is dirty (was already).');
|
|
Assert.AreEqual(1, subToC.NotifyCount, 'subToC should NOT be notified again.');
|
|
end;
|
|
|
|
procedure TTestMycDirtyFlag.TestChain_A_B_C_AllReset_RetriggerA_FullPropagation;
|
|
var
|
|
flagA, flagB, flagC: IMycDirty;
|
|
subToC: TMockSubscriber;
|
|
subscriptionB_A, subscriptionC_B, subscriptionMock_C: TState.TSubscription;
|
|
begin
|
|
flagA := CreateAndPrepareDirtyFlag(False);
|
|
flagB := CreateAndPrepareDirtyFlag(False);
|
|
flagC := CreateAndPrepareDirtyFlag(False);
|
|
subToC := TMockSubscriber.Create;
|
|
|
|
subscriptionMock_C := flagC.State.Subscribe(subToC);
|
|
subscriptionC_B := flagB.State.Subscribe(flagC);
|
|
subscriptionB_A := flagA.State.Subscribe(flagB);
|
|
|
|
// All are clean. Trigger A.
|
|
flagA.Notify; // A dirty -> B dirty -> C dirty. subToC notified.
|
|
Assert.IsTrue(flagC.State.IsSet, 'Flag C dirty after first full propagation.');
|
|
Assert.AreEqual(1, subToC.NotifyCount, 'subToC notified once.');
|
|
|
|
// Reset all
|
|
flagA.Reset;
|
|
flagB.Reset;
|
|
flagC.Reset;
|
|
Assert.IsFalse(flagA.State.IsSet, 'Flag A clean.');
|
|
Assert.IsFalse(flagB.State.IsSet, 'Flag B clean.');
|
|
Assert.IsFalse(flagC.State.IsSet, 'Flag C clean.');
|
|
// subToC count is still 1, it's not notified by resets.
|
|
|
|
// Re-trigger A
|
|
flagA.Notify; // A (was clean) -> A dirty. A notifies B.
|
|
// B.Notify() called. B was clean -> B dirty. B notifies C.
|
|
// C.Notify() called. C was clean -> C dirty. C notifies subToC.
|
|
|
|
Assert.IsTrue(flagA.State.IsSet, 'Flag A dirty on re-trigger.');
|
|
Assert.IsTrue(flagB.State.IsSet, 'Flag B dirty on re-trigger.');
|
|
Assert.IsTrue(flagC.State.IsSet, 'Flag C dirty on re-trigger.');
|
|
Assert.AreEqual(2, subToC.NotifyCount, 'subToC should be notified a second time.');
|
|
end;
|
|
|
|
procedure TTestMycDirtyFlag.TestChain_A_B_C_TriggerA_ResetA_TriggerA_EnsureCNotifiedCorrectly;
|
|
var
|
|
flagA, flagB, flagC: IMycDirty;
|
|
subToC: TMockSubscriber;
|
|
subscriptionB_A, subscriptionC_B, subscriptionMock_C: TState.TSubscription;
|
|
begin
|
|
flagA := CreateAndPrepareDirtyFlag(False);
|
|
flagB := CreateAndPrepareDirtyFlag(False);
|
|
flagC := CreateAndPrepareDirtyFlag(False);
|
|
subToC := TMockSubscriber.Create;
|
|
|
|
subscriptionMock_C := flagC.State.Subscribe(subToC);
|
|
subscriptionC_B := flagB.State.Subscribe(flagC);
|
|
subscriptionB_A := flagA.State.Subscribe(flagB);
|
|
|
|
// 1. Trigger A
|
|
flagA.Notify; // A, B, C become dirty. subToC notified (count = 1).
|
|
Assert.IsTrue(flagC.State.IsSet, 'C is dirty after 1st trigger.');
|
|
Assert.AreEqual(1, subToC.NotifyCount, 'subToC count after 1st trigger.');
|
|
|
|
// 2. Reset A (B and C remain dirty)
|
|
flagA.Reset;
|
|
Assert.IsFalse(flagA.State.IsSet, 'A is clean.');
|
|
Assert.IsTrue(flagB.State.IsSet, 'B remains dirty.');
|
|
Assert.IsTrue(flagC.State.IsSet, 'C remains dirty.');
|
|
|
|
// 3. Trigger A again
|
|
flagA.Notify; // A (was clean) becomes dirty. A notifies B.
|
|
// B.Notify() is called. B was dirty. B.Notify sets FFlag to true (no change), returns false.
|
|
// B does NOT notify C. C remains dirty. subToC is NOT notified again.
|
|
Assert.IsTrue(flagA.State.IsSet, 'A is dirty after 2nd trigger.');
|
|
Assert.IsTrue(flagB.State.IsSet, 'B is still dirty (state did not flip to clean then dirty).');
|
|
Assert.IsTrue(flagC.State.IsSet, 'C is still dirty.');
|
|
Assert.AreEqual(1, subToC.NotifyCount, 'subToC count should remain 1.');
|
|
end;
|
|
|
|
initialization
|
|
TDUnitX.RegisterTestFixture(TTestMycDirtyFlag);
|
|
end.
|