{
 Test with:
   ./testcodetools --suite=TTestFindDeclaration
   ./testcodetools --suite=TestFindDeclaration_Basic
   ./testcodetools --suite=TestFindDeclaration_ClassOf
   ./testcodetools --suite=TestFindDeclaration_With
   ./testcodetools --suite=TestFindDeclaration_NestedClasses
   ./testcodetools --suite=TestFindDeclaration_ClassHelper
   ./testcodetools --suite=TestFindDeclaration_TypeHelper
   ./testcodetools --suite=TestFindDeclaration_ObjCClass
   ./testcodetools --suite=TestFindDeclaration_ObjCCategory
   ./testcodetools --suite=TestFindDeclaration_Generics
   ./testcodetools --suite=TestFindDeclaration_FileAtCursor

 FPC tests:
   ./testcodetools --suite=TestFindDeclaration_FPCTests
   ./testcodetools --suite=TestFindDeclaration_FPCTests --filemask=t*.pp
   ./testcodetools --suite=TestFindDeclaration_FPCTests --filemask=tchlp41.pp
 Laz tests:
   ./testcodetools --suite=TestFindDeclaration_LazTests
   ./testcodetools --suite=TestFindDeclaration_LazTests --filemask=t*.pp
   ./testcodetools --suite=TestFindDeclaration_LazTests --filemask=tdefaultproperty1.pp
}
unit TestIdentCompletion;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, fpcunit, testregistry, FileProcs, LazFileUtils, LazLogger,
  CodeToolManager, ExprEval, CustomCodeTool, FindDeclarationTool,
  KeywordFuncLists, CodeCache, IdentCompletionTool, CodeTree,
  TestFindDeclaration;

type

  { TTestIdentCompletion }

  TTestIdentCompletion = class(TCustomTestFindDeclaration)
  private
    procedure CheckCodeContext(Context: TCodeContextInfoItem; MarkerName: string
      );
  published
    procedure Test_GetValuesOfCaseVariable_Enum;
    procedure Test_GetValuesOfCaseVariable_Enum_Amp;
    procedure Test_FindCodeContext_ProcParams;
    procedure Test_FindCodeContext_ProcParams_Amp;
    procedure Test_FindCodeContext_ProcParams_NoClosingBracket;
    procedure Test_FindCodeContext_ProcTypeParams;
    procedure Test_FindCodeContext_AttributeParams;
    procedure Test_GatherIdentifiers_ProcParams_String1;
    procedure Test_GatherIdentifiers_ProcParams_String2;
    procedure Test_GatherIdentifiers_ProcParams_String3;
  end;

implementation

{ TTestIdentCompletion }

procedure TTestIdentCompletion.CheckCodeContext(Context: TCodeContextInfoItem; MarkerName: string);
var
  Marker: TFDMarker;
begin
  AssertNotNull('CheckCodeContext: missing context for #'+MarkerName,Context);
  AssertEquals('CheckCodeContext: Context.Expr.Desc for #'+MarkerName,
    ExpressionTypeDescNames[xtContext],ExpressionTypeDescNames[Context.Expr.Desc]);
  if MainTool<>Context.Expr.Context.Tool then
    Fail('CheckCodeContext: Context.Expr.Context.Tool for #'+MarkerName+' expected "'+MainTool.MainFilename+'", but found "'+Context.Expr.Context.Tool.MainFilename+'"');
  Marker:=FindMarker(MarkerName,'#');
  AssertNotNull('CheckCodeContext: missing marker #'+MarkerName,Marker);
  AssertEquals('CheckCodeContext: Context.Expr.Context.Node.StartPos for #'+MarkerName,
    Marker.CleanPos,Context.Expr.Context.Node.StartPos);
end;

procedure TTestIdentCompletion.Test_GetValuesOfCaseVariable_Enum;
var
  List: TStrings;
begin
  List:=TStringList.Create;
  try
    Code.Source:=
    'type TEnum = (red,green);'+LineEnding
    +'var e: TEnum;'+LineEnding
    +'begin'+LineEnding
    +'  case e of'+LineEnding
    +'end.';
    List.Clear;
    if not CodeToolBoss.GetValuesOfCaseVariable(Code,8,4,List) then begin
      Fail('GetValuesOfCaseVariable failed on case enum');
    end;
    //writeln('TTestIdentCompletion.Test_GetValuesOfCaseVariable_Enum ',List.Text);
    AssertEquals('case enum count',2,List.Count);
    AssertEquals('case enum[0]','red',List[0]);
    AssertEquals('case enum[1]','green',List[1]);
  finally
    List.Free;
  end;
end;

procedure TTestIdentCompletion.Test_GetValuesOfCaseVariable_Enum_Amp;
var
  List: TStrings;
begin
  List:=TStringList.Create;
  try
    Code.Source:=
    'type &array = (abc, &array, amp, &aaa);'+LineEnding
    +'var e: &array;'+LineEnding
    +'begin'+LineEnding
    +'  case e of'+LineEnding
    +'end.';
    List.Clear;
    if not CodeToolBoss.GetValuesOfCaseVariable(Code,8,4,List) then begin
      Fail('GetValuesOfCaseVariable failed on case enum');
    end;
    //writeln('TTestIdentCompletion.Test_GetValuesOfCaseVariable_Enum ',List.Text);
    AssertEquals('case enum count',4,List.Count);
    AssertEquals('case enum[0]','abc',List[0]);
    AssertEquals('case enum[1]','&array',List[1]);
    AssertEquals('case enum[2]','amp',List[2]);
    AssertEquals('case enum[3]','&aaa',List[3]);
  finally
    List.Free;
  end;
end;

procedure TTestIdentCompletion.Test_FindCodeContext_ProcParams;
var
  SrcMark: TFDMarker;
  CursorPos: TCodeXYPosition;
  CodeContexts: TCodeContextInfo;
begin
  StartProgram;
  Add([
  '{#a}procedure DoIt(i, j: longint);',
  'begin',
  'end;',
  '{#b}procedure DoIt(s, h: string);',
  'begin',
  'end;',
  'begin',
  '  DoIt(3,{#c}4);',
  'end.']);
  ParseSimpleMarkers(Code);
  SrcMark:=FindMarker('c','#');
  AssertNotNull('missing src marker #c',SrcMark);
  MainTool.CleanPosToCaret(SrcMark.CleanPos,CursorPos);
  CodeContexts:=nil;
  try
    if not CodeToolBoss.FindCodeContext(Code,CursorPos.X,CursorPos.Y,CodeContexts)
    then begin
      WriteSource(CursorPos);
      Fail('CodeToolBoss.FindCodeContext');
    end;
    AssertEquals('CodeContexts.Count',2,CodeContexts.Count);
    //for i:=0 to CodeContexts.Count-1 do
    //  debugln(['TTestIdentCompletion.Test_FindCodeContext_ProcParams ',i,' ',CodeContexts[i].AsDebugString(true)]);
    CheckCodeContext(CodeContexts[0],'b');
    CheckCodeContext(CodeContexts[1],'a');
  finally
    CodeContexts.Free;
  end;
end;

procedure TTestIdentCompletion.Test_FindCodeContext_ProcParams_Amp;
var
  SrcMark: TFDMarker;
  CursorPos: TCodeXYPosition;
  CodeContexts: TCodeContextInfo;
begin
  StartProgram;
  Add([
  'type &array = byte;',
  '{#a}procedure &procedure(&type, j: &array);',
  'begin',
  'end;',
  '{#b}procedure &procedure(s, &h: string);',
  'begin',
  'end;',
  'begin',
  '  &procedure(3,{#c}4);',
  'end.']);
  ParseSimpleMarkers(Code);
  SrcMark:=FindMarker('c','#');
  AssertNotNull('missing src marker #c',SrcMark);
  MainTool.CleanPosToCaret(SrcMark.CleanPos,CursorPos);
  CodeContexts:=nil;
  try
    if not CodeToolBoss.FindCodeContext(Code,CursorPos.X,CursorPos.Y,CodeContexts)
    then begin
      WriteSource(CursorPos);
      Fail('CodeToolBoss.FindCodeContext');
    end;
    AssertEquals('CodeContexts.Count',2,CodeContexts.Count);
    //for i:=0 to CodeContexts.Count-1 do
    //  debugln(['TTestIdentCompletion.Test_FindCodeContext_ProcParams ',i,' ',CodeContexts[i].AsDebugString(true)]);
    CheckCodeContext(CodeContexts[0],'b');
    CheckCodeContext(CodeContexts[1],'a');
  finally
    CodeContexts.Free;
  end;
end;

procedure TTestIdentCompletion.Test_FindCodeContext_ProcParams_NoClosingBracket;
var
  SrcMark: TFDMarker;
  CursorPos: TCodeXYPosition;
  CodeContexts: TCodeContextInfo;
begin
  StartProgram;
  Add([
  '{#a}procedure DoIt(i, j: longint);',
  'begin',
  'end;',
  'begin',
  '  DoIt({#c}',
  'end.']);
  ParseSimpleMarkers(Code);
  SrcMark:=FindMarker('c','#');
  AssertNotNull('missing src marker #c',SrcMark);
  MainTool.CleanPosToCaret(SrcMark.CleanPos,CursorPos);
  CodeContexts:=nil;
  try
    if not CodeToolBoss.FindCodeContext(Code,CursorPos.X,CursorPos.Y,CodeContexts)
    then begin
      WriteSource(CursorPos);
      Fail('CodeToolBoss.FindCodeContext');
    end;
    AssertEquals('CodeContexts.Count',1,CodeContexts.Count);
    //for i:=0 to CodeContexts.Count-1 do
    //  debugln(['TTestIdentCompletion.Test_FindCodeContext_ProcParams ',i,' ',CodeContexts[i].AsDebugString(true)]);
    CheckCodeContext(CodeContexts[0],'a');
  finally
    CodeContexts.Free;
  end;
end;

procedure TTestIdentCompletion.Test_FindCodeContext_ProcTypeParams;
var
  SrcMark: TFDMarker;
  CursorPos: TCodeXYPosition;
  CodeContexts: TCodeContextInfo;
begin
  StartProgram;
  Add([
  'type',
  '  TProc = procedure(i,j: longint);',
  'var {#p}p: TProc;',
  'begin',
  '  p(3,{#c}4);',
  'end.']);
  ParseSimpleMarkers(Code);
  SrcMark:=FindMarker('c','#');
  AssertNotNull('missing src marker #c',SrcMark);
  MainTool.CleanPosToCaret(SrcMark.CleanPos,CursorPos);
  CodeContexts:=nil;
  try
    if not CodeToolBoss.FindCodeContext(Code,CursorPos.X,CursorPos.Y,CodeContexts)
    then begin
      WriteSource(CursorPos);
      Fail('CodeToolBoss.FindCodeContext');
    end;
    AssertEquals('CodeContexts.Count',1,CodeContexts.Count);
    //for i:=0 to CodeContexts.Count-1 do
    //  debugln(['TTestIdentCompletion.Test_FindCodeContext_ProcParams ',i,' ',CodeContexts[i].AsDebugString(true)]);
    CheckCodeContext(CodeContexts[0],'p');
  finally
    CodeContexts.Free;
  end;
end;

procedure TTestIdentCompletion.Test_FindCodeContext_AttributeParams;
var
  SrcMark: TFDMarker;
  CursorPos: TCodeXYPosition;
  CodeContexts: TCodeContextInfo;
begin
  StartProgram;
  Add([
  '{$modeswitch prefixedattributes}',
  'type',
  '  BirdAttribute = class',
  '    {#a}constructor Create; overload;',
  '    {#b}constructor Create(i,j: longint); overload;',
  '  end;',
  '  [Bird({#c})]',
  '  TColor = 1..3;',
  'begin',
  'end.']);
  ParseSimpleMarkers(Code);
  SrcMark:=FindMarker('c','#');
  AssertNotNull('missing src marker #c',SrcMark);
  MainTool.CleanPosToCaret(SrcMark.CleanPos,CursorPos);
  CodeContexts:=nil;
  try
    if not CodeToolBoss.FindCodeContext(Code,CursorPos.X,CursorPos.Y,CodeContexts)
    then begin
      WriteSource(CursorPos);
      Fail('CodeToolBoss.FindCodeContext');
    end;
    //for i:=0 to CodeContexts.Count-1 do
    //  debugln(['TTestIdentCompletion.Test_FindCodeContext_ProcParams ',i,' ',CodeContexts[i].AsDebugString(true)]);
    AssertEquals('CodeContexts.Count',3,CodeContexts.Count);
    CheckCodeContext(CodeContexts[0],'b');
    CheckCodeContext(CodeContexts[1],'a');
    // last entry is the default TObject.Create in unit objpas
  finally
    CodeContexts.Free;
  end;
end;

procedure TTestIdentCompletion.Test_GatherIdentifiers_ProcParams_String1;
var
  SrcMark: TFDMarker;
  CursorPos: TCodeXYPosition;
  CodeContexts: TCodeContextInfo;
begin
  StartProgram;
  Add([
  'begin',
  '  writeln({#c}'''');',
  'end.']);
  ParseSimpleMarkers(Code);
  SrcMark:=FindMarker('c','#');
  AssertNotNull('missing src marker #c',SrcMark);
  MainTool.CleanPosToCaret(SrcMark.CleanPos,CursorPos);
  CodeContexts:=nil;
  try
    CodeToolBoss.GatherIdentifiers(Code,CursorPos.X,CursorPos.Y);
    AssertTrue('CodeToolBoss.GatherIdentifiers: '+CodeToolBoss.ErrorMessage,CodeToolBoss.ErrorId=0);
  finally
    CodeContexts.Free;
  end;
end;

procedure TTestIdentCompletion.Test_GatherIdentifiers_ProcParams_String2;
var
  SrcMark: TFDMarker;
  CursorPos: TCodeXYPosition;
  CodeContexts: TCodeContextInfo;
begin
  StartProgram;
  Add([
  'begin',
  '  writeln({#c}'''');',
  'end.']);
  ParseSimpleMarkers(Code);
  SrcMark:=FindMarker('c','#');
  AssertNotNull('missing src marker #c',SrcMark);
  MainTool.CleanPosToCaret(SrcMark.CleanPos+1,CursorPos);
  CodeContexts:=nil;
  try
    CodeToolBoss.GatherIdentifiers(Code,CursorPos.X,CursorPos.Y);
    AssertTrue('CodeToolBoss.GatherIdentifiers: '+CodeToolBoss.ErrorMessage,CodeToolBoss.ErrorId=0);
  finally
    CodeContexts.Free;
  end;
end;

procedure TTestIdentCompletion.Test_GatherIdentifiers_ProcParams_String3;
var
  SrcMark: TFDMarker;
  CursorPos: TCodeXYPosition;
  CodeContexts: TCodeContextInfo;
begin
  StartProgram;
  Add([
  'begin',
  '  writeln(''''{#c});',
  'end.']);
  ParseSimpleMarkers(Code);
  SrcMark:=FindMarker('c','#');
  AssertNotNull('missing src marker #c',SrcMark);
  MainTool.CleanPosToCaret(SrcMark.CleanPos,CursorPos);
  CodeContexts:=nil;
  try
    CodeToolBoss.GatherIdentifiers(Code,CursorPos.X,CursorPos.Y);
    AssertTrue('CodeToolBoss.GatherIdentifiers: '+CodeToolBoss.ErrorMessage,CodeToolBoss.ErrorId=0);
  finally
    CodeContexts.Free;
  end;
end;

initialization
  RegisterTests([TTestIdentCompletion]);
end.

