unit tccomponent;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, fpcunit, testregistry;

type

  { TEventSink }

  TEventSink = Class(TObject)
    FEventCount : Integer;
    FLastSender : TObject;
    Procedure Event(Sender : TObject); virtual;
    Procedure ResetEvent;
  end;

  { TNotification }
  
  TNotification = Class(TCollectionItem)
  Public
    ASender,
    AComponent : TComponent;
    AOperation : TOperation;
  end;

  { TNotificationSink }

  TNotificationSink = Class(TObject)
  private
    Fevents : TCollection;
    function GetNot(Index : Integer): TNotification;
  Public
    Destructor Destroy; override;
    procedure Notification(Sender, AComponent: TComponent; Operation: TOperation); virtual;
    Procedure Reset;
    Function EventCount : Integer;
    Property Notifications [Index : Integer] : TNotification Read GetNot;
  end;

  { TMyComponent }

  TNotificationEvent = procedure (Sender : TComponent; AComponent: TComponent; Operation: TOperation) of object;

  TMyComponent = Class(TComponent)
  private
    FOnDestroy: TNotifyEvent;
    FOnNotify: TNotificationEvent;
  Public
    Destructor Destroy; override;
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    Property OnDestroy : TNotifyEvent Read FOnDestroy Write FOnDestroy;
    Property OnNotification : TNotificationEvent Read FOnNotify Write FOnNotify;
  end;

  { TTestTComponentBase }

  TTestTComponentBase = class(TTestCase)
  protected
    FRoot : TMyComponent;
    Procedure CreateComponents(ACount : Integer);
    Procedure CreateComponents(ACount : Integer; Const BaseName : String);
    Procedure CreateComponents(ACount : Integer; AClass : TComponentClass);
    Procedure CreateComponents(ACount : Integer; AClass : TComponentClass; Const BaseName : String);
    procedure SetUp; override;
    procedure TearDown; override; 
  end;
  
  { TTestTComponent }

  TTestTComponent = Class(TTestTComponentBase)
  private
    procedure TestDoubleName;
    procedure TestTextName;
    procedure TestNumberName;
    procedure TestNumberTextName;
  Published
    Procedure TestCreate;
    Procedure TestName;
    procedure TestIdentiFierName;
    procedure TestIdentiFierNameTwo;
    procedure TestIdentiFierNameThree;
    procedure TestIdentiFierNameFour;
    procedure TestOwner;
    procedure TestChildren;
    Procedure TestDestroyChild;
    Procedure TestDestroyChildren;
    Procedure TestUniqueName;
    Procedure TestRemoveComponent;
  end;
  
  { TTestTComponentNotifies }

  TTestTComponentNotifies = Class(TTestTComponentBase)
  Protected
    N : TNotificationSink;
    procedure SetUp; override;
    procedure TearDown; override;
  Published
    Procedure TestInsertNotification;
    Procedure TestRemoveNotification;
  end;


implementation

procedure TTestTComponentBase.CreateComponents(ACount: Integer);
begin
  CreateComponents(ACount,'');
end;

procedure TTestTComponentBase.CreateComponents(ACount: Integer;
  const BaseName: String);
begin
  CreateComponents(ACount,TMyComponent,BaseName);
end;

procedure TTestTComponentBase.CreateComponents(ACount: Integer;
  AClass: TComponentClass);
begin
  CreateComponents(ACount,AClass,'');
end;

procedure TTestTComponentBase.CreateComponents(ACount: Integer;
  AClass: TComponentClass; const BaseName: String);
  
Var
  I : Integer;
  C : TComponent;
  
begin
  For I:=0 to ACount-1 do
    begin
    C:=TMyComponent.Create(FRoot);
    If (BaseName<>'') then
      C.Name:=BaseName+IntToStr(I+1);
    end;
end;

procedure TTestTComponentBase.SetUp; 
begin
  FRoot:=TMyComponent.Create(Nil);
  FRoot.Name:='Root';
end; 

procedure TTestTComponentBase.TearDown; 
begin
  FreeAndNil(FRoot);
end; 

{ TTestTComponent }

procedure TTestTComponent.TestCreate;
begin
  FreeAndNil(Froot);
  FRoot:=TMyComponent.Create(Nil);
  AssertEquals('Empty name','',FRoot.Name);
  AssertEquals('No owned components',0,FRoot.ComponentCount);
  If (FRoot.ComponentState<>[]) then
    Fail('Componentstate is not empty');
  If (FRoot.Owner<>Nil) then
    Fail('Owner is not nil');
end;

procedure TTestTComponent.TestName;
begin
  AssertEquals('Name is Root','Root',FRoot.Name);
end;

procedure TTestTComponent.TestOwner;

Var
  C : TComponent;

begin
  C:=TComponent.Create(FRoot);
  If (C.Owner<>FRoot) then
    Fail('Owner not saved after create');
end;

procedure TTestTComponent.TestChildren;
begin
  CreateComponents(3,'Child');
  AssertEquals('Componentcount is 3',3,FRoot.ComponentCount);
  AssertEquals('Child component 0 is child1','Child1',FRoot.Components[0].Name);
  AssertEquals('Child component 1 is child2','Child2',FRoot.Components[1].Name);
  AssertEquals('Child component 2 is child3','Child3',FRoot.Components[2].Name);
end;

procedure TTestTComponent.TestDestroyChild;

Var
  S : TEventSink;

begin
  CreateComponents(1);
  S:=TEventSink.Create;
  try
    TMyComponent(FRoot.Components[0]).OnDestroy:=@S.Event;
    FreeAndNil(FRoot);
    AssertEquals('One child destroyed',1,S.FEventcount);
    If (S.FLastSender=Nil) then
      Fail('No sender passed');
  finally
    S.Free;
  end;
end;

procedure TTestTComponent.TestDestroyChildren;

Var
  S : TEventSink;
  I : Integer;

begin
  CreateComponents(3);
  S:=TEventSink.Create;
  try
    For I:=0 to 2 do
      TMyComponent(FRoot.Components[I]).OnDestroy:=@S.Event;
    FreeAndNil(FRoot);
    AssertEquals('One child destroyed',3,S.FEventcount);
    If (S.FLastSender=Nil) then
      Fail('No sender passed');
  finally
    S.Free;
  end;
end;

procedure TTestTComponent.TestDoubleName;

begin
  FRoot.Components[1].Name:='Child1';
end;

procedure TTestTComponent.TestUniqueName;
begin
  CreateComponents(3,'Child');
  AssertException('Unique name',EComponentError,@TestDoubleName);
end;

procedure TTestTComponent.TestRemoveComponent;

Var
  C : TComponent;

begin
  CreateComponents(1);
  C:=FRoot.Components[0];
  FRoot.RemoveComponent(C);
  Try
    AssertEquals('No components left',0,FRoot.ComponentCount);
    AssertSame('Component has no owner',Nil,C.Owner);
  Finally
    C.Free;
  end;
end;


procedure TTestTComponent.TestTextName;

begin
  FRoot.Name:='Child 1';
end;

procedure TTestTComponent.TestNumberName;
begin
  FRoot.Name:='1';
end;

procedure TTestTComponent.TestNumberTextName;
begin
  FRoot.Name:='1Too';
end;

procedure TTestTComponent.TestIdentiFierName;
begin
  AssertException('Identifier name',EComponentError,@TestTextName);
end;

procedure TTestTComponent.TestIdentiFierNameTwo;

begin
  AssertException('Identifier name',EComponentError,@TestNumberTextName);
end;

procedure TTestTComponent.TestIdentiFierNameThree;
begin
  AssertException('Identifier name',EComponentError,@TestNumberName);
end;

procedure TTestTComponent.TestIdentiFierNameFour;

Var
  Failed : Boolean;

begin
  Failed:=False;
  Try
    FRoot.Name:='Some1';
  except
    Failed:=True;
  end;
  If Failed then
    Fail('No identifier ending on 1 accepted ?');
end;

{ TMyComponent }

destructor TMyComponent.Destroy;
begin
  If Assigned(FOnDestroy) then
    FOnDestroy(Self);
  inherited Destroy;
end;

procedure TMyComponent.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  If Assigned(FOnNotify) then
    FOnNotify(Self, AComponent, Operation);
  inherited Notification(AComponent, Operation);
end;

{ TEventSink }

procedure TEventSink.Event(Sender: TObject);
begin
  Inc(FEventCount);
  FLastSender:=Sender;
end;

procedure TEventSink.ResetEvent;
begin
  FLastSender:=Nil;
  FEventCount:=0;
end;

{ TNotificationSink }

function TNotificationSink.GetNot(Index : Integer): TNotification;
begin
  If Assigned(FEvents) then
    Result:=TNotification(FEvents.Items[Index])
  else
    Result:=Nil;
end;

destructor TNotificationSink.Destroy;
begin
  FreeAndNil(FEvents);
  inherited Destroy;
end;

procedure TNotificationSink.Notification(Sender, AComponent: TComponent;
  Operation: TOperation);
  
Var
  N : TNotification;
  
begin
  If (Fevents=Nil) then
    FEvents:=TCollection.Create(TNotification);
  N:=FEvents.Add as TNotification;
  N.AComponent:=AComponent;
  N.ASender:=Sender;
  N.AOperation:=Operation;
end;

procedure TNotificationSink.Reset;
begin
  FreeAndNil(FEvents);
end;

function TNotificationSink.EventCount: Integer;
begin
  If (Fevents<>Nil) then
    Result:=FEvents.Count
  else
    Result:=0;
end;

{ TTestTComponentNotifies }

procedure TTestTComponentNotifies.SetUp;
begin
  inherited SetUp;
  N:=TNotificationSink.Create;
  FRoot.OnNotification:=@N.Notification;
end;

procedure TTestTComponentNotifies.TearDown;
begin
  FreeAndNil(N);
  inherited TearDown;
end;

procedure TTestTComponentNotifies.TestInsertNotification;

Var
  E : TNotification;

begin
  CreateComponents(1);
  AssertEquals('One notification received',1,N.EventCount);
  E:=N.Notifications[0];
  AssertEquals('Insert notification received',Ord(opInsert),Ord(E.AOperation));
end;

procedure TTestTComponentNotifies.TestRemoveNotification;

Var
  C : TComponent;
  E : TNotification;

begin
  CreateComponents(1);
  N.Reset;
  C:=FRoot.Components[0];
  FRoot.RemoveComponent(C);
  Try
    AssertEquals('One notification received',1,N.EventCount);
    E:=N.Notifications[0];
  Finally
    C.Free;
  end;
end;


initialization

  RegisterTests([TTestTComponent,TTestTComponentNotifies]);
end.

