Browse Source

fcl-css: tests for :first-of-type, :last-of-type

mattias 2 years ago
parent
commit
c3d0d3edd3
2 changed files with 253 additions and 40 deletions
  1. 24 0
      packages/fcl-css/src/fpcssresolver.pas
  2. 229 40
      packages/fcl-css/tests/tccssresolver.pp

+ 24 - 0
packages/fcl-css/src/fpcssresolver.pas

@@ -50,6 +50,10 @@ const
   CSSPseudoID_Empty = 2; // :empty
   CSSPseudoID_FirstChild = 3; // :first-child
   CSSPseudoID_LastChild = 4; // :last-child
+  CSSPseudoID_OnlyChild = 5; // :only-child
+  CSSPseudoID_FirstOfType = 6; // :first-of-type
+  CSSPseudoID_LastOfType = 7; // :last-of-type
+  CSSPseudoID_OnlyOfType = 8; // :only-of-type
 
 type
   TCSSMsgID = int64;
@@ -81,6 +85,8 @@ type
     function GetCSSPreviousSibling: TCSSNode;
     function GetCSSChildCount: integer;
     function GetCSSChild(const anIndex: integer): TCSSNode;
+    function GetCSSNextOfType: TCSSNode;
+    function GetCSSPreviousOfType: TCSSNode;
     function HasCSSAttribute(const AttrID: TCSSNumericalID): boolean;
     function GetCSSAttribute(const AttrID: TCSSNumericalID): TCSSString;
     function HasCSSPseudoAttribute(const AttrID: TCSSNumericalID): boolean;
@@ -415,6 +421,20 @@ begin
   CSSPseudoID_LastChild:
     if TestNode.GetCSSNextSibling=nil then
       Result:=CSSSpecifityClass;
+  CSSPseudoID_OnlyChild:
+    if (TestNode.GetCSSNextSibling=nil)
+        and (TestNode.GetCSSPreviousSibling=nil) then
+      Result:=CSSSpecifityClass;
+  CSSPseudoID_FirstOfType:
+    if TestNode.GetCSSPreviousOfType=nil then
+      Result:=CSSSpecifityClass;
+  CSSPseudoID_LastOfType:
+    if TestNode.GetCSSNextOfType=nil then
+      Result:=CSSSpecifityClass;
+  CSSPseudoID_OnlyOfType:
+    if (TestNode.GetCSSNextOfType=nil)
+        and (TestNode.GetCSSPreviousOfType=nil) then
+      Result:=CSSSpecifityClass;
   else
     TestNode.GetCSSPseudoAttribute(PseudoID);
   end;
@@ -772,6 +792,10 @@ begin
       ':empty': Result:=CSSPseudoID_Empty;
       ':first-child': Result:=CSSPseudoID_FirstChild;
       ':last-child': Result:=CSSPseudoID_LastChild;
+      ':only-child': Result:=CSSPseudoID_OnlyChild;
+      ':first-of-type': Result:=CSSPseudoID_FirstOfType;
+      ':last-of-type': Result:=CSSPseudoID_LastOfType;
+      ':only-of-type': Result:=CSSPseudoID_OnlyOfType;
       end;
     end;
 

+ 229 - 40
packages/fcl-css/tests/tccssresolver.pp

@@ -97,6 +97,8 @@ type
     function GetCSSPreviousSibling: TCSSNode; virtual;
     function GetCSSChildCount: integer; virtual;
     function GetCSSChild(const anIndex: integer): TCSSNode; virtual;
+    function GetCSSNextOfType: TCSSNode; virtual;
+    function GetCSSPreviousOfType: TCSSNode; virtual;
     function HasCSSAttribute(const AttrID: TCSSNumericalID): boolean; virtual;
     function GetCSSAttribute(const AttrID: TCSSNumericalID): TCSSString; virtual;
     function HasCSSPseudoAttribute(const AttrID: TCSSNumericalID): boolean; virtual;
@@ -213,27 +215,30 @@ type
     procedure Test_Selector_AttributeBeginsWithHyphen;
     procedure Test_Selector_AttributeContainsWord;
     procedure Test_Selector_AttributeContainsSubstring;
-    // ToDo: all
+    // ToDo: "all"
+
+    // pseudo attributes
     procedure Test_Selector_Root;
     procedure Test_Selector_Empty;
     procedure Test_Selector_FirstChild;
     procedure Test_Selector_LastChild;
-    // ToDo: :first-of-type
-    // ToDo: :last-of-type
+    procedure Test_Selector_OnlyChild;
+    // ToDo: :nth-child(n)
+    // ToDo: :nth-last-child(n)
+    procedure Test_Selector_FirstOfType;
+    procedure Test_Selector_LastOfType;
+    procedure Test_Selector_OnlyOfType;
+    // ToDo: :nth-of-type(n)
+    // ToDo: :nth-last-of-type(n)
     // ToDo: :not(selector)
-    // ToDo: nth-child(n)
-    // ToDo: nth-last-child(n)
-    // ToDo: nth-of-type(n)
-    // ToDo: nth-last-of-type(n)
-    // ToDo: :only-of-type
-    // ToDo: :only-child
-    // ToDo: :defined
     // ToDo: div:has(>img)
     // ToDo: div:has(+img)
     // ToDo: :is()
     // ToDo: :where()
     // ToDo: :lang()
+
     // ToDo: inline style
+
     // ToDo: specifity
   end;
 
@@ -648,17 +653,17 @@ end;
 
 procedure TTestCSSResolver.Test_Selector_Empty;
 var
-  Div1, Div11, Div2: TDemoButton;
+  Div1, Div11, Div2: TDemoDiv;
 begin
   Doc.Root:=TDemoNode.Create(nil);
 
-  Div1:=TDemoButton.Create(Doc);
+  Div1:=TDemoDiv.Create(Doc);
   Div1.Parent:=Doc.Root;
 
-  Div11:=TDemoButton.Create(Doc);
+  Div11:=TDemoDiv.Create(Doc);
   Div11.Parent:=Div1;
 
-  Div2:=TDemoButton.Create(Doc);
+  Div2:=TDemoDiv.Create(Doc);
   Div2.Parent:=Doc.Root;
 
   Doc.Style:=LinesToStr([
@@ -670,28 +675,28 @@ begin
   AssertEquals('Root.Top','',Doc.Root.Top);
   AssertEquals('Div1.Left','x',Div1.Left);
   AssertEquals('Div1.Top','',Div1.Top);
-  AssertEquals('Div11.Left','1px',Div1.Left);
-  AssertEquals('Div11.Top','2px',Div1.Top);
-  AssertEquals('Div2.Left','1px',Div1.Left);
-  AssertEquals('Div2.Top','',Div1.Top);
+  AssertEquals('Div11.Left','1px',Div11.Left);
+  AssertEquals('Div11.Top','2px',Div11.Top);
+  AssertEquals('Div2.Left','1px',Div2.Left);
+  AssertEquals('Div2.Top','',Div2.Top);
 end;
 
 procedure TTestCSSResolver.Test_Selector_FirstChild;
 var
-  Div1, Div11, Div12, Div2: TDemoButton;
+  Div1, Div11, Div12, Div2: TDemoDiv;
 begin
   Doc.Root:=TDemoNode.Create(nil);
 
-  Div1:=TDemoButton.Create(Doc);
+  Div1:=TDemoDiv.Create(Doc);
   Div1.Parent:=Doc.Root;
 
-  Div11:=TDemoButton.Create(Doc);
+  Div11:=TDemoDiv.Create(Doc);
   Div11.Parent:=Div1;
 
-  Div12:=TDemoButton.Create(Doc);
+  Div12:=TDemoDiv.Create(Doc);
   Div12.Parent:=Div1;
 
-  Div2:=TDemoButton.Create(Doc);
+  Div2:=TDemoDiv.Create(Doc);
   Div2.Parent:=Doc.Root;
 
   Doc.Style:=LinesToStr([
@@ -703,30 +708,30 @@ begin
   AssertEquals('Root.Top','',Doc.Root.Top);
   AssertEquals('Div1.Left','1px',Div1.Left);
   AssertEquals('Div1.Top','',Div1.Top);
-  AssertEquals('Div11.Left','1px',Div1.Left);
-  AssertEquals('Div11.Top','2px',Div1.Top);
-  AssertEquals('Div12.Left','',Div1.Left);
-  AssertEquals('Div12.Top','',Div1.Top);
-  AssertEquals('Div2.Left','',Div1.Left);
-  AssertEquals('Div2.Top','',Div1.Top);
+  AssertEquals('Div11.Left','1px',Div11.Left);
+  AssertEquals('Div11.Top','2px',Div11.Top);
+  AssertEquals('Div12.Left','',Div12.Left);
+  AssertEquals('Div12.Top','',Div12.Top);
+  AssertEquals('Div2.Left','',Div2.Left);
+  AssertEquals('Div2.Top','',Div2.Top);
 end;
 
 procedure TTestCSSResolver.Test_Selector_LastChild;
 var
-  Div1, Div11, Div12, Div2: TDemoButton;
+  Div1, Div11, Div12, Div2: TDemoDiv;
 begin
   Doc.Root:=TDemoNode.Create(nil);
 
-  Div1:=TDemoButton.Create(Doc);
+  Div1:=TDemoDiv.Create(Doc);
   Div1.Parent:=Doc.Root;
 
-  Div11:=TDemoButton.Create(Doc);
+  Div11:=TDemoDiv.Create(Doc);
   Div11.Parent:=Div1;
 
-  Div12:=TDemoButton.Create(Doc);
+  Div12:=TDemoDiv.Create(Doc);
   Div12.Parent:=Div1;
 
-  Div2:=TDemoButton.Create(Doc);
+  Div2:=TDemoDiv.Create(Doc);
   Div2.Parent:=Doc.Root;
 
   Doc.Style:=LinesToStr([
@@ -738,12 +743,166 @@ begin
   AssertEquals('Root.Top','',Doc.Root.Top);
   AssertEquals('Div1.Left','',Div1.Left);
   AssertEquals('Div1.Top','',Div1.Top);
-  AssertEquals('Div11.Left','',Div1.Left);
-  AssertEquals('Div11.Top','',Div1.Top);
-  AssertEquals('Div12.Left','6px',Div1.Left);
-  AssertEquals('Div12.Top','7px',Div1.Top);
-  AssertEquals('Div2.Left','6px',Div1.Left);
-  AssertEquals('Div2.Top','',Div1.Top);
+  AssertEquals('Div11.Left','',Div11.Left);
+  AssertEquals('Div11.Top','',Div11.Top);
+  AssertEquals('Div12.Left','6px',Div12.Left);
+  AssertEquals('Div12.Top','7px',Div12.Top);
+  AssertEquals('Div2.Left','6px',Div2.Left);
+  AssertEquals('Div2.Top','',Div2.Top);
+end;
+
+procedure TTestCSSResolver.Test_Selector_OnlyChild;
+var
+  Div1, Div11, Div2: TDemoDiv;
+  Button12: TDemoButton;
+begin
+  Doc.Root:=TDemoNode.Create(nil);
+
+  Div1:=TDemoDiv.Create(Doc);
+  Div1.Parent:=Doc.Root;
+
+  Div11:=TDemoDiv.Create(Doc);
+  Div11.Parent:=Div1;
+
+  Div2:=TDemoDiv.Create(Doc);
+  Div2.Parent:=Doc.Root;
+
+  Button12:=TDemoButton.Create(Doc);
+  Button12.Parent:=Div2;
+
+  Doc.Style:=LinesToStr([
+  ':only-child { left: 8px; }',
+  'div:only-child { top: 9px; }',
+  '']);
+  Doc.ApplyStyle;
+  AssertEquals('Root.Left','8px',Doc.Root.Left);
+  AssertEquals('Root.Top','',Doc.Root.Top);
+  AssertEquals('Div1.Left','',Div1.Left);
+  AssertEquals('Div1.Top','',Div1.Top);
+  AssertEquals('Div11.Left','8px',Div11.Left);
+  AssertEquals('Div11.Top','9px',Div11.Top);
+  AssertEquals('Div2.Left','',Div2.Left);
+  AssertEquals('Div2.Top','',Div2.Top);
+  AssertEquals('Button12.Left','8px',Button12.Left);
+  AssertEquals('Button12.Top','',Button12.Top);
+end;
+
+procedure TTestCSSResolver.Test_Selector_FirstOfType;
+var
+  Div1, Div11, Div13, Div2: TDemoDiv;
+  Button12: TDemoButton;
+begin
+  Doc.Root:=TDemoNode.Create(nil);
+
+  Div1:=TDemoDiv.Create(Doc);
+  Div1.Parent:=Doc.Root;
+
+  Div11:=TDemoDiv.Create(Doc);
+  Div11.Parent:=Div1;
+
+  Button12:=TDemoButton.Create(Doc);
+  Button12.Parent:=Div1;
+
+  Div13:=TDemoDiv.Create(Doc);
+  Div13.Parent:=Div1;
+
+  Div2:=TDemoDiv.Create(Doc);
+  Div2.Parent:=Doc.Root;
+
+  Doc.Style:=LinesToStr([
+  ':first-of-type { left: 6px; }',
+  'div:first-of-type { top: 7px; }',
+  '']);
+  Doc.ApplyStyle;
+  AssertEquals('Root.Left','6px',Doc.Root.Left);
+  AssertEquals('Root.Top','',Doc.Root.Top);
+  AssertEquals('Div1.Left','6px',Div1.Left);
+  AssertEquals('Div1.Top','7px',Div1.Top);
+  AssertEquals('Div11.Left','6px',Div11.Left);
+  AssertEquals('Div11.Top','7px',Div11.Top);
+  AssertEquals('Button12.Left','6px',Button12.Left);
+  AssertEquals('Button12.Top','',Button12.Top);
+  AssertEquals('Div13.Left','',Div13.Left);
+  AssertEquals('Div13.Top','',Div13.Top);
+  AssertEquals('Div2.Left','',Div2.Left);
+  AssertEquals('Div2.Top','',Div2.Top);
+end;
+
+procedure TTestCSSResolver.Test_Selector_LastOfType;
+var
+  Div1, Div11, Div13, Div2: TDemoDiv;
+  Button12: TDemoButton;
+begin
+  Doc.Root:=TDemoNode.Create(nil);
+
+  Div1:=TDemoDiv.Create(Doc);
+  Div1.Parent:=Doc.Root;
+
+  Div11:=TDemoDiv.Create(Doc);
+  Div11.Parent:=Div1;
+
+  Button12:=TDemoButton.Create(Doc);
+  Button12.Parent:=Div1;
+
+  Div13:=TDemoDiv.Create(Doc);
+  Div13.Parent:=Div1;
+
+  Div2:=TDemoDiv.Create(Doc);
+  Div2.Parent:=Doc.Root;
+
+  Doc.Style:=LinesToStr([
+  ':last-of-type { left: 6px; }',
+  'div:last-of-type { top: 7px; }',
+  '']);
+  Doc.ApplyStyle;
+  AssertEquals('Root.Left','6px',Doc.Root.Left);
+  AssertEquals('Root.Top','',Doc.Root.Top);
+  AssertEquals('Div1.Left','',Div1.Left);
+  AssertEquals('Div1.Top','',Div1.Top);
+  AssertEquals('Div11.Left','',Div11.Left);
+  AssertEquals('Div11.Top','',Div11.Top);
+  AssertEquals('Button12.Left','6px',Button12.Left);
+  AssertEquals('Button12.Top','',Button12.Top);
+  AssertEquals('Div13.Left','6px',Div13.Left);
+  AssertEquals('Div13.Top','7px',Div13.Top);
+  AssertEquals('Div2.Left','6px',Div2.Left);
+  AssertEquals('Div2.Top','7px',Div2.Top);
+end;
+
+procedure TTestCSSResolver.Test_Selector_OnlyOfType;
+var
+  Div1, Div11, Div2: TDemoDiv;
+  Button12: TDemoButton;
+begin
+  Doc.Root:=TDemoNode.Create(nil);
+
+  Div1:=TDemoDiv.Create(Doc);
+  Div1.Parent:=Doc.Root;
+
+  Div11:=TDemoDiv.Create(Doc);
+  Div11.Parent:=Div1;
+
+  Button12:=TDemoButton.Create(Doc);
+  Button12.Parent:=Div1;
+
+  Div2:=TDemoDiv.Create(Doc);
+  Div2.Parent:=Doc.Root;
+
+  Doc.Style:=LinesToStr([
+  ':only-of-type { left: 6px; }',
+  'div:only-of-type { top: 7px; }',
+  '']);
+  Doc.ApplyStyle;
+  AssertEquals('Root.Left','6px',Doc.Root.Left);
+  AssertEquals('Root.Top','',Doc.Root.Top);
+  AssertEquals('Div1.Left','',Div1.Left);
+  AssertEquals('Div1.Top','7px',Div1.Top);
+  AssertEquals('Div11.Left','6px',Div11.Left);
+  AssertEquals('Div11.Top','7px',Div11.Top);
+  AssertEquals('Button12.Left','6px',Button12.Left);
+  AssertEquals('Button12.Top','',Button12.Top);
+  AssertEquals('Div2.Left','',Div2.Left);
+  AssertEquals('Div2.Top','',Div2.Top);
 end;
 
 { TDemoDiv }
@@ -1122,6 +1281,36 @@ begin
   Result:=Nodes[anIndex];
 end;
 
+function TDemoNode.GetCSSNextOfType: TCSSNode;
+var
+  i: Integer;
+  MyID: TCSSNumericalID;
+begin
+  i:=GetCSSIndex+1;
+  MyID:=CSSTypeID;
+  while i<NodeCount do
+  begin
+    if Nodes[i].CSSTypeID=MyID then
+      exit(Nodes[i]);
+    inc(i);
+  end;
+end;
+
+function TDemoNode.GetCSSPreviousOfType: TCSSNode;
+var
+  i: Integer;
+  MyID: TCSSNumericalID;
+begin
+  i:=GetCSSIndex-1;
+  MyID:=CSSTypeID;
+  while i>=0 do
+  begin
+    if Nodes[i].CSSTypeID=MyID then
+      exit(Nodes[i]);
+    dec(i);
+  end;
+end;
+
 function TDemoNode.HasCSSAttribute(const AttrID: TCSSNumericalID): boolean;
 begin
   Result:=(AttrID>=DemoAttrIDBase) and (AttrID<=DemoAttrIDBase+ord(High(TDemoNodeAttribute)));