unit GrphTest;

interface

uses
  Windows, Messages, SysUtils, Types, Classes, Forms, Dialogs, Controls,
  AppEvnts;

type
  TGrphTest = class
  private
    class var TestIndex: Integer;
    class var AppEvents: TApplicationEvents;
    class var FKeepStore: Boolean;
    class procedure NextTest; static;
    class procedure AppMessage(var Message: TMsg; var Handled: Boolean);
  public
    class procedure RunTests; static;
  end;

const
  GrphTestLayout =
    '<?xml version="1.0" encoding="UTF-8" ?>' +
    '<layout xmlns="http://www.rejbrand.org/2025/algosim-layout">' +
    '  <title>Visualisation self test</title>' +
    '  <stack orientation="horizontal">' +
    '    <stack orientation="vertical">' +
    '      <panel class="Console" default="true" />' +
    '      <panel class="Debug log" />' +
    '    </stack>' +
    '    <stack orientation="vertical">' +
    '      <panel class="Identifiers" />' +
    '      <panel class="Visual object manager" />' +
    '    </stack>' +
    '  </stack>' +
    '</layout>';

implementation

uses
  MainForm, Math, StrUtils, VisMgrForm, UxPanel;

const
  Tests: array of string =
    [
      'plot(y = sin(x), −π, π)',
      'plot(cos(x) < y < 2⋅cos(x), −π, π)',
      'plot([−2⋅π, 2⋅π] @ (t ↦ ❨5⋅cos(5⋅t)⋅sin(5⋅t), 5⋅sin(5⋅t)⋅sin(2⋅t), t❩))',
      'plot(x^4 + x^3⋅y + y^4 = arctan(x + y))',
      'f ≔ (x, y) ↦ 6⋅sin(x⋅y/4)⋅arctan(2⋅√(x^2+y^2))⋅exp(−(x^2+y^2)/32); diagram("scalar field"); hf := heatmap(f, ❨−10, 10❩, ❨−10, 10❩); AdjustVisual(hf, "fill-opacity": 0.5); ContourPlot(f, ❨−10, 10❩, ❨−10, 10❩)',
      'plot(sin(x)^4 + sin(y)^4 = 1, ❨−π, π❩, ❨−π, π❩)',
      'plot(x^2 + y^2 = .1)',
      'plot(x^2 + y^2 = 1)',
      'plot(x^4 + y^4 = 1)',
      'plot(x^2 + (y − (x^2)^(1/3))^2 = 1)',
      'plot(sin(x^2) − sin(y^2) = x + y)',
      'ContourPlot((x, y) ↦ sin(x)^4 + sin(y)^4, ❨−π, π❩, ❨−π, π❩)',
      'ContourPlot((x, y) ↦ sin(x)^2 − sin(y)^2, ❨−π, π❩ / 2, ❨−π, π❩ / 4, 50)',
      'ScatterPlot(❨1, 1❩, ❨2, 5❩, ❨3, −2❩)',
      'ScatterPlot(graph(sin, −2⋅π, 2⋅π))',
      'ScatterPlot(10⋅RandomMatrix(1000, 3))',
      'ScatterPlot(graph((x, y) ↦ sin(√(x^2 + y^2)), ❨−10, 10❩, ❨−10, 10❩))',
      'F ≔ (x, y, z) ↦ ❨x, y, z, integer(rgb(x / 10, y / 10, z / 10)), 5.0❩;'#13#10'ScatterPlot([0, 10, 1]^3 @ F)',
      'F ≔ (x, y, z) ↦ ❨x ⋅ RandomReal(), y ⋅ RandomReal(), z ⋅ RandomReal(), integer(rgb(x / 10, y / 10, z / 10)), RandomReal(5.0)❩;'#13#10'ScatterPlot([0, 10, 0.5]^3 @ F)',
      'F ≔ (θ, φ) ↦ ❨10⋅sin(θ)⋅cos(φ), 10⋅sin(θ)⋅sin(φ), 10⋅cos(θ), integer(hsv(180⋅(θ+2⋅φ)/π, 1.0, 1.0)), 5.0❩;'#13#10'ScatterPlot([0, π, π/50] × [0, 2⋅π, π/50] @ F)',
      'LinePlot(❨1, 1❩, ❨2, 5❩, ❨3, −2❩)',
      'x ≔ LinePlot(graph(sin, −2⋅π, 2⋅π));'#13#10'AdjustVisual(x, "line color": "red", "line width": 3)',
      'AreaChart(❨1, 1❩, ❨2, 5❩, ❨3, −2❩)',
      'x ≔ AreaChart(graph(sin, −2⋅π, 2⋅π));'#13#10'AdjustVisual(x,'#13#10'  "lines": true, "line color": "red", "line width": 3,'#13#10'  "fill color": "red", "fill opacity": 0.25)',
      'ScatterPlot(graph(sin, −2⋅π, 2⋅π))',
      'A ≔ RandomIntMatrix(5, −10, 11);'#13#10'MatrixPlot(A)§',
      'MatrixPlot(A, "gold")§',
      'MatrixPlot(A, ''("blue", "red"), true)§',
      'MatrixPlot(A, ''("blue", "white", "red"), true)',
      'A ≔ RandomMatrix(100);'#13#10'MatrixPlot(A)§',
      'MatrixPlot(A^5)§',
      'MatrixPlot(A ⋅ A*)§',
      'MatrixPlot(A ⋅ A*, (t ↦ hsv(80⋅t, t, t)), false, 400, 400)',
      'n ≔ 500; ComputePixmap(n, n, x, y, hsv(x⋅360/pred(n), 1, y/pred(n)))',
      'ComputePixmap(500, 500, x, y, rgb(x/499, y/499, 0.5))',
      'ComputePixmap(500, 500, x, y, hsv(x^2/100 + y/10 − sin((x−20⋅cos(y/25))/200)⋅x⋅y/50, abs(cos(x/500)), abs(cos(x/500))))',
      'D ≔ ClearDiagram("vector field");'#13#10'sf ≔ heatmap((x, y) ↦ x^2 + y^2, ❨−10, 10❩, ❨−10, 10❩, ''("white", "red"));'#13#10+'vf ≔ VectorField((x, y) ↦ ❨2⋅x, 2⋅y❩, ❨−10, 10❩, ❨−10, 10❩)',
      'heatmap((x, y) ↦ sin(√(x^2 + y^2)), ❨−10, 10❩, ❨−10, 10❩, "red")',
      'sf ≔ heatmap((x, y) ↦ sin(√(x^2 + y^2)), ❨−10, 10❩, ❨−10, 10❩, ''("blue", "red"))',
      'sf ≔ heatmap((x, y) ↦ sin(√(x^2 + y^2)), ❨−10, 10❩, ❨−10, 10❩, ''("blue", "white", "red"))',
      'sf ≔ heatmap((x, y) ↦ sin(√(x^2 + y^2)), ❨−10, 10❩, ❨−10, 10❩, x ↦ hsv(360⋅x, 1, 1))',
      'D ≔ ClearDiagram("field");'#13#10'disk(❨0, 0❩, .3);'#13#10'F ≔ (x, y) ↦ ❨−y, x❩ / (x^2 + y^2)^1.5;'#13#10'vf ≔ VectorField(F, ❨−2, 2, .5❩, ❨−2, 2, .5❩);'#13#10 + 'AdjustVisual(D.view, "ymin": -2, "ymax": 2);'#13#10'AdjustVisual(vf, "arrow-scale": 4, "auto-normalize": true)§',
      'C ≔ cylinder(❨0, 0, −10❩, ❨1, 1, 20❩);'#13#10'F ≔ (x, y, z) ↦ ❨−y, x, 0❩ / (x^2 + y^2)^.5;'#13#10 + 'VF ≔ VectorField(F, ❨−5, 5, 2❩, ❨−5, 5, 2❩, ❨−10, 10, 2❩)',
      'S ≔ scene("VF");'#13#10 +
        'F ≔ (u, v) ↦ ❨(1 + .5⋅v⋅cos(u/2))⋅cos(u), (1 + .5⋅v⋅cos(u/2))⋅sin(u), .5⋅v⋅sin(u/2)❩;'#13#10 +
        'M ≔ surf([−1, 1] × [0, 2⋅π] @ ((v, u) ↦ ❨(1 + .5⋅v⋅cos(u/2))⋅cos(u), (1 + .5⋅v⋅cos(u/2))⋅sin(u), .5⋅v⋅sin(u/2)❩));'#13#10 +
        'AdjustVisual(M, "show parameter curves": true, "parameter curve counts": ❨16, 64❩, "line width": .75, "unisided": true);'#13#10 +
        'X ≔ (u, v) ↦ ❨−(1 + .5⋅v⋅cos(u/2))⋅sin(u) − .25⋅v⋅sin(u/2)⋅cos(u), (1 + .5⋅v⋅cos(u/2))⋅cos(u) − .25⋅v⋅sin(u/2)⋅sin(u), .25⋅v⋅cos(u/2)❩;'#13#10 +
        'Y ≔ (u, v) ↦ ❨.5⋅cos(u/2)⋅cos(u), .5⋅cos(u/2)⋅sin(u), .5⋅sin(u/2)❩;'#13#10 +
        'N ≔ (u, v) ↦ normalized(X(u, v) × Y(u, v));'#13#10 +
        'VF ≔ VectorField([0, 2⋅π, π/32] × [−1, 1, 1/8] @ ((u, v) ↦ (P ≔ F(u, v); V ≔ N(u, v); ❨P[1], P[2], P[3], V[1], V[2], V[3]❩)));'#13#10 +
        'AdjustVisual(VF, "size": 0.1, "anchor point": 1.0, "color": "gold"); AdjustVisual(S.view, "r": 4.0)',
      'VF ≔ VectorField([−4⋅π, 4⋅π, π/4] @ (t ↦ ❨cos(t), sin(t), t/4, −sin(t), cos(t), 1/4❩));'#13#10'AdjustVisual(VF, "size": .75)',
      'curve([0, 32⋅π] @ (t ↦ t⋅❨cos(t), sin(t)❩))',
      'f ≔ t ↦ (e^sin(t) − 2⋅cos(4⋅t) + sin((2⋅t − π)/24)^5) ⋅ ❨cos(t), sin(t)❩;'#13#10'B ≔ curve([0, 100, 0.01] @ f);'#13#10'AdjustVisual(B, "line color": "salmon", "line width": 3)',
      'f ≔ t ↦ (e^sin(t) − 2⋅cos(4⋅t) + sin((2⋅t − π)/24)^5) ⋅ ❨cos(t), sin(t)❩;'#13#10+
        'g ≔ (u, v) ↦ ❨u, v, u^2 / 10❩;'#13#10+
        'scene("butterflies");'#13#10+
        'L ≔ compute(❨RandomReal(−25, 25), RandomReal(−100, 40), RandomReal(−10, 10),'#13#10 +
        '             integer(RandomColor())❩, k, 1, 20);'#13#10 +
        'ForEach(L, v, curve([0, 100, 0.01] @ (t ↦ g(f(t))) @ ((x, y, z) ↦ ❨x, y, z, 0❩ + v)));'#13#10 +
        'scene("butterflies")',
      'surf(z = sin(x^2/10 + y^2/10))',
      'surf(z = 8⋅sin(x)⋅cos(y)⋅exp(−(x^2 + y^2)/20))',
      'surf(z = 8⋅sin(x)⋅cos(y)⋅exp(−(x^2 + y^2)/20), ❨−10, 10❩, ❨−10, 10❩, hsv(10⋅(8 − z), 1.0, 1.0))',
      'x ≔ surf([0, π] × [0, 2⋅π] @ ((θ, φ) ↦ 10⋅❨sin(θ)⋅cos(φ), sin(θ)⋅sin(φ), cos(θ)❩));'#13#10'AdjustVisual(x, "show parameter curves": true)',
      'surf([−π, π] × [−π, π] @ ((θ, φ) ↦ ❨φ⋅sin(θ), φ⋅cos(θ), θ⋅cos(φ), integer(rgb((π+φ)/(2⋅π), (π+φ)/(2⋅π), (2⋅π+θ)/(3⋅π)))❩))',
      'x ≔ surf([0, 2⋅π] × [0, 2⋅π] @ ((u, v) ↦ 4⋅❨(4 + cos(v))⋅cos(u), (4 + cos(v))⋅sin(u), sin(v)❩));'#13#10'AdjustVisual(x, "show parameter curves": true)',
      'BarChart("Cats": 4, "Dogs": 7, "Rats": 5, "Rabbits": 4)',
      'BarChart(sort(frequencies(compute(RandomInt(1000000)^2, n, 1, 1000000) @ digits @ first)))',
      'BarChart(SortBy(frequencies(filter(characters(UpperCase(ExampleData("Alice in Wonderland"))), ChrIsLetter)), (x ↦ −x[2])))',
      'PieChart("Cats": 4, "Dogs": 7, "Rats": 5, "Rabbits": 4)',
      'Alice ≔ ExampleData("Alice in Wonderland");'#13#10 +
        'πc ≔ PieChart('#13#10 +
        '  ''(''("letters", ChrIsLetter),'#13#10 +
        '    ''("punctuation", ChrIsPunctuation),'#13#10 +
        '    ''("whitespace", ChrIsWhitespace)'#13#10 +
        '   )'#13#10 +
        '  @ (x ↦ ''(x[1], count(Alice, x[2])))'#13#10 +
        ');'#13#10 +
        'AdjustVisual(πc, "label position": 20, "value labels": false);',
      'H ≔ histogram(compute(mean(compute(RandomReal(), n, 1, 10)), n, 1, 1000000));'#13#10'AdjustVisual(H, "bin width": 0.01)§',
      'AdjustVisual(H, "bin width": 0.001)',
      'd ≔ diagram("trig");'#13#10 +
        'AdjustVisual(d.view, "xmin": −2⋅π, "xmax": 2⋅π, "ymin": −1.5, "ymax": 1.5);'#13#10 +
        's ≔ LinePlot(graph(sin, −2⋅π, 2⋅π));'#13#10 +
        'c ≔ LinePlot(graph(cos, −2⋅π, 2⋅π));'#13#10 +
        'AdjustVisual(s, "line color": "blue");'#13#10 +
        'AdjustVisual(c, "line color": "red");'#13#10 +
        'st ≔ text(❨5, sin(5)❩, "sin");'#13#10 +
        'ct ≔ text(❨5, cos(5)❩, "cos");'#13#10 +
        'AdjustVisual(st, "text color": "blue");'#13#10 +
        'AdjustVisual(ct, "text color": "red");',
      'scene("text");'#13#10 +
        'words ≔ first(SortBy(frequencies(words(ExampleData("Alice in Wonderland"))), (x ↦ −x[2])), 75);'#13#10 +
        'ForEach(words, w,'#13#10 +
        '  ('#13#10 +
        '    t ≔ text(10⋅RandomSignedVector(3), w[1], ❨1, 0, 0❩);'#13#10 +
        '    AdjustVisual(t, "font name": "Georgia", "font size": round(200 ⋅ √(w[2]/words[1][2])), "color": RandomColor(), "opacity": .7);'#13#10 +
        '  )'#13#10 +
        ');',
      'EmbedPixmap(ExampleData("Sally"), ❨3, 5❩)',
      'scene("Dog and teapot");'#13#10 +
        'S ≔ ExampleData("Sally");'#13#10 +
        'EmbedPixmap(S, ❨8, 2, 0❩, 10, 0, ❨−.1, −.1, 1❩, 45);'#13#10 +
        'solid("teapot", ❨6, 9, 0❩)',
      'LineSegment(❨1, 4❩, ❨6, −2❩)',
      'disk(❨4, 3❩, 3)',
      'circle(❨4, 3❩, 3)',
      'rectangle(❨4, 0❩, 5, 2)',
      'sphere(❨0, 0, 0❩, 5)',
      'scene("balls"); for(k, 1, 10, sphere(10⋅RandomVector(3), RandomReal())); scene("balls")',
      'scene("balls"); for(k, −10, 10, if(k ≠ 0, sphere(❨0, 0, k❩, abs(k)/2.5))); scene("balls")',
      'ellipsoid()',
      'ellipsoid(❨5, 5, 0❩, ❨2, 2, 8❩)',
      'ellipsoid(❨5, 5, 0❩, ❨2, 2, 8❩, ❨1, 2, 0❩)',
      'cone()',
      'cone(❨2, 2, 0❩, ❨1, 1, 2❩)',
      'cone(❨5, 5, 0❩, ❨1, 1, 5❩, ❨−1, −1, 0❩)',
      'cylinder(❨0, 0, 0❩, ❨5, 5, 5❩)',
      'cylinder(❨5, 5, 0❩, ❨.5, .5, 10❩, ❨−.2, .1, 1❩)',
      'x ≔ cylinder(❨0, 0, 0❩, ❨1, 1, 5❩);'#13#10'AdjustVisual(x, "show parameter curves": true)',
      'disk3D()',
      'plane()',
      'InfinitePlane()',
      'CoordinateAxes(❨8, 8, 5❩, ❨.7, .3, 0❩, ❨0, 1, 0❩, ❨0, .3, .7❩)',
      'solid("cube", ❨0, 0, 0❩, ❨5, 5, 5❩)',
      'solid("tetrahedron", ❨0, 0, 0❩, ❨5, 5, 5❩)',
      'solid("pyramid", ❨0, 0, 0❩, ❨5, 5, 5❩)',
      'solid("octahedron", ❨0, 0, 0❩, ❨5, 5, 5❩)',
      'solid("icosahedron", ❨0, 0, 0❩, ❨5, 5, 5❩)',
      'solid("dodecahedron", ❨0, 0, 0❩, ❨5, 5, 5❩)',
      'solid("cylinder", ❨0, 0, 0❩, ❨5, 5, 5❩)',
      'solid("cone", ❨0, 0, 0❩, ❨5, 5, 5❩)',
      's ≔ solid("teapot", ❨0, 0, 0❩, ❨2, 2, 2❩); AdjustVisual(s, "color": "coral")',
      'icosahedron ≔'#13#10 +
        ''#13#10 +
        '"'#13#10 +
        'v 0 -1 -1.61803398874989485'#13#10 +
        'v 0 -1 +1.61803398874989485'#13#10 +
        'v 0 +1 -1.61803398874989485'#13#10 +
        'v 0 +1 +1.61803398874989485'#13#10 +
        'v -1 -1.61803398874989485 0'#13#10 +
        'v -1 +1.61803398874989485 0'#13#10 +
        'v +1 -1.61803398874989485 0'#13#10 +
        'v +1 +1.61803398874989485 0'#13#10 +
        'v -1.61803398874989485 0 -1'#13#10 +
        'v -1.61803398874989485 0 +1'#13#10 +
        'v +1.61803398874989485 0 -1'#13#10 +
        'v +1.61803398874989485 0 +1'#13#10 +
        ''#13#10 +
        'f 12 8 4'#13#10 +
        'f 12 11 8'#13#10 +
        'f 4 8 6'#13#10 +
        'f 8 3 6'#13#10 +
        'f 11 3 8'#13#10 +
        'f 4 6 10'#13#10 +
        'f 6 9 10'#13#10 +
        'f 10 9 5'#13#10 +
        'f 10 5 2'#13#10 +
        'f 5 7 2'#13#10 +
        'f 5 1 7'#13#10 +
        'f 7 1 11'#13#10 +
        'f 9 3 1'#13#10 +
        'f 1 3 11'#13#10 +
        'f 6 3 9'#13#10 +
        'f 4 10 2'#13#10 +
        'f 4 2 12'#13#10 +
        'f 7 11 12'#13#10 +
        'f 2 7 12'#13#10 +
        'f 9 1 5'#13#10 +
        '";'#13#10 +
        ''#13#10 +
        'model(icosahedron)',
      'r ≔ plot(−√(1 − x^8) < y < √(1 − x^8), −1, 1, 0.0001)§',
      'AdjustVisual(r,'#13#10'  "lines": true, "line color": "red", "line width": 3,'#13#10'  "fill color": "red", "fill opacity": .25)§',
      'AdjustVisual(r,'#13#10'  "lines": false,'#13#10'  "fill color": "black", "fill opacity": .25)',
      'Scene ≔ ClearScene("bars");'#13#10 +
        '"H ≔ surf([−π, π] × [−20, 20] @ ((u, v) ↦ ❨u⋅cos(v), u⋅sin(v), v❩))";'#13#10 +
        'r ≔ v ↦ ❨−π⋅cos(v), −π⋅sin(v), v❩;'#13#10 +
        't ≔ v ↦ ❨cos(v), sin(v), 0❩;'#13#10 +
        'B ≔ v ↦ solid("cylinder", r(v), ❨0.1, 0.1, 2⋅π❩, t(v));'#13#10 +
        'Bars ≔ [−4, 4, 0.02] @ B;'#13#10 +
        'ForEach(Bars, b, AdjustVisual(b, "visible": false));'#13#10 +
        'n ≔ 1;'#13#10 +
        'prev ≔ last(Bars);'#13#10 +
        'ForEach('#13#10 +
        '  Bars,'#13#10 +
        '  b,'#13#10 +
        '  ('#13#10 +
        '    AdjustVisual(prev, "visible": mod(n, 10) = 0, "color": "silver");'#13#10 +
        '    prev ≔ AdjustVisual(b, "visible": true, "color": "red");'#13#10 +
        '    inc(n);'#13#10 +
        '    sleep(0.05)'#13#10 +
        '  )'#13#10 +
        ');',
      'diagram();'#13#10 +
        'trig ≔ diagram("trig");'#13#10 +
        's ≔ plot(y = sin(x));'#13#10 +
        'c ≔ plot(y = cos(x));'#13#10 +
        'at ≔ plot(y = arctan(x));'#13#10 +
        'AdjustVisual(trig.view, "ymin": −2, "ymax": +2)§',
      'RemoveVisual(at)',
      's ≔ diagram("arrows"); AdjustVisual(s.view, "ymin": -1.2, "ymax": +1.2); AdjustVisual(s, "auto normalize": true); [0, 15, 1] @ (n ↦ arrow(❨cos(2⋅π/16 ⋅ n), sin(2⋅π/16 ⋅ n)❩))',
      's ≔ scene("arrows"); [0, 15, 1] @ (n ↦ arrow(❨cos(2⋅π/16 ⋅ n), sin(2⋅π/16 ⋅ n), 1❩))',
      's ≔ scene("test"); for(k, 1, 10000, solid("icosahedron", 100⋅RandomSignedVector(3))); scene("test")',
      's ≔ ClearScene("test"); for(k, 1, 100, solid("teapot", 50⋅RandomSignedVector(3))); scene("test")',
      's ≔ scene("test"); for(k, 1, 1000, sphere(20⋅RandomSignedVector(3))); AdjustVisual(s, "effects": ''("underwater"))',
      'scene("test"); s ≔ solid("teapot"); AdjustVisual(s, "animation speed": 20.0)§',
      'd ≔ ClearDiagram("end"); AdjustVisual(d.axes.x, "visible": false); AdjustVisual(d.axes.y, "visible": false); t ≔ text(❨0, 0❩, "The End"); AdjustVisual(t, "anchor point": "center", "font name": "Cambria", "font size": 48, "italic": true)'
    ];

{ TGrphTest }

class procedure TGrphTest.AppMessage(var Message: TMsg; var Handled: Boolean);
begin
  if (Message.message = WM_KEYDOWN) and (Message.wParam = VK_F9) then
  begin
    if GetKeyState(VK_CONTROL) < 0 then
    begin
      AlgosimMainForm.Kernel.EvaluateAsync('try(unprotect(AutoTest)); AutoTest ≔ true; protect(AutoTest); sleep(1);');
      while TestIndex < High(Tests) do
        NextTest;
    end
    else
      NextTest;
    Handled := True;
  end;
end;

class procedure TGrphTest.NextTest;
begin

  Inc(TestIndex);

  if TestIndex > High(Tests) then
  begin
    Application.MainForm.Close;
  end
  else
  begin

    var Test := Tests[TestIndex];

    AlgosimMainForm.Kernel.EvaluateAsync('TestIndex ≔ ' + TestIndex.ToString + ';');

    if not FKeepStore then
    begin
      AlgosimMainForm.Kernel.EvaluateAsync('diagram("@panel"); diagram(); scene();');
      AlgosimMainForm.Kernel.EvaluateAsync('VisualObjects() @ (r ↦ try(RemoveVisual(r)));');
      AlgosimMainForm.Kernel.EvaluateAsync('filter(panels(), (p ↦ p.type = "Image viewer")) @ ClosePanel;');
    end;

    AlgosimMainForm.Kernel.EvaluateAsync('cls(); print(format("Test %1 of %2.", ''(TestIndex + 1, TestCount)));');

    FKeepStore := Test.EndsWith('§');
    if FKeepStore then Delete(Test, Test.Length, 1);

    AlgosimMainForm.Kernel.EvaluateAsync('TestExpression ≔ "' + Test.Replace('"', '""') + '"; print(TestExpression);');
    AlgosimMainForm.Kernel.EvaluateAsync(Test);
    AlgosimMainForm.Kernel.EvaluateAsync('if(AutoTest, sleep(1.5));');

    if not FKeepStore then
      AlgosimMainForm.Kernel.EvaluateAsync('variables() @ (x ↦ try(delete(string(x))));');

    if TestIndex = High(Tests) then
    begin
      AlgosimMainForm.Kernel.EvaluateAsync('EndTime ≔ now(); protect(EndTime);');
      AlgosimMainForm.Kernel.EvaluateAsync('FinalMemUsage ≔ MemoryUsage(); protect(FinalMemUsage);');
      AlgosimMainForm.Kernel.EvaluateAsync('FinalHandles ≔ DebugObject("win32 handles"); protect(FinalHandles);');
      AlgosimMainForm.Kernel.EvaluateAsync('FinalRglCtxCount ≔ DebugObject("rgl ctx count"); protect(FinalRglCtxCount);');
      AlgosimMainForm.Kernel.EvaluateAsync('print("Duration: " + SecondsBetween(StartTime, EndTime));');
      AlgosimMainForm.Kernel.EvaluateAsync('print("Press F9 to exit.");')
    end
    else
      AlgosimMainForm.Kernel.EvaluateAsync('print("Press F9 to continue.");');

  end;

end;

class procedure TGrphTest.RunTests;
begin
  AppEvents := TApplicationEvents.Create(Application);
  AppEvents.OnMessage := AppMessage;
  AlgosimMainForm.WindowState := TWindowState.wsMaximized;
  AlgosimMainForm.NoConsoleInput := True;
  AlgosimMainForm.OutputToActiveInstance := True;
  AlgosimMainForm.Kernel.EvaluateAsync('DebugObject("rgl log");');
  AlgosimMainForm.Kernel.EvaluateAsync('diagram("@panel"); diagram();');
  AlgosimMainForm.Kernel.EvaluateAsync('TestCount ≔ ' + Length(Tests).ToString + '; protect(TestCount);');
  AlgosimMainForm.Kernel.EvaluateAsync('StartTime ≔ now(); protect(StartTime);');
  AlgosimMainForm.Kernel.EvaluateAsync('InitialMemUsage ≔ MemoryUsage(); protect(InitialMemUsage);');
  AlgosimMainForm.Kernel.EvaluateAsync('InitialHandles ≔ DebugObject("win32 handles"); protect(InitialHandles);');
  AlgosimMainForm.Kernel.EvaluateAsync('try(AutoTest ≔ false; protect(AutoTest));');
  TestIndex := Pred(Low(Tests));
  NextTest;
end;

end.
