1 module auxil.test; 2 3 version(unittest) import unit_threaded : Name; 4 5 import auxil.model; 6 7 @safe private 8 struct PrettyPrintingVisitor 9 { 10 import std.experimental.allocator.mallocator : Mallocator; 11 import automem.vector : Vector; 12 13 Vector!(char, Mallocator) output; 14 private Vector!(char, Mallocator) _indentation; 15 DefaultVisitor default_visitor; 16 alias default_visitor this; 17 18 this(float size) @nogc 19 { 20 default_visitor = DefaultVisitor(size); 21 } 22 23 auto processItem(T...)(T msg) 24 { 25 () @trusted { 26 output ~= _indentation[]; 27 import nogc.conv : text; 28 output ~= text(msg, "\n")[]; 29 } (); 30 } 31 32 void indent() @nogc @trusted 33 { 34 _indentation ~= '\t'; 35 } 36 37 void unindent() @nogc @trusted 38 { 39 if (_indentation.length) 40 _indentation.popBack; 41 } 42 43 void enterTree(Order order, Data, Model)(auto ref const(Data) data, ref Model model) 44 { 45 position = 0; 46 } 47 48 void enterNode(Order order, Data, Model)(ref const(Data) data, ref Model model) 49 { 50 import std.conv : to; 51 import auxil.traits : hasRenderHeader; 52 53 static if (hasRenderHeader!data) 54 { 55 import auxil.model : FixedAppender; 56 FixedAppender!512 app; 57 data.renderHeader(app); 58 () @trusted { processItem(app[]); } (); 59 } 60 else 61 processItem("Caption: ", Data.stringof); 62 } 63 64 void processLeaf(Order order, Data, Model)(ref const(Data) data, ref Model model) 65 { 66 processItem(data); 67 } 68 } 69 70 version(unittest) @Name("aggregates_trivial") 71 @safe 72 unittest 73 { 74 static struct Test 75 { 76 float f = 7.7; 77 int i = 8; 78 string s = "some text"; 79 } 80 81 static struct StructWithStruct 82 { 83 double d = 8.8; 84 long l = 999; 85 Test t; 86 } 87 88 static class TestClass 89 { 90 91 } 92 93 static struct StructWithPointerAndClass 94 { 95 double* d; 96 TestClass tc; 97 } 98 99 static struct StructWithNestedClass 100 { 101 TestClass tc; 102 } 103 104 auto visitor = PrettyPrintingVisitor(9); 105 auto d = StructWithStruct(); 106 auto m = makeModel(d); 107 m.visitForward(d, visitor); 108 assert(m.size == 10); 109 d.d = 0; 110 d.l = 1; 111 d.t.f = 2; 112 d.t.i = 3; 113 d.t.s = "s"; 114 m.visitForward(d, visitor); 115 m.collapsed = false; 116 m.visitForward(d, visitor); 117 m.t.collapsed = false; 118 m.visitForward(d, visitor); 119 } 120 121 version(unittest) @Name("static_array") 122 @nogc 123 unittest 124 { 125 version(Windows) {} 126 else { 127 import core.stdc.locale; 128 setlocale(LC_NUMERIC, "C"); 129 } 130 float[3] d = [1.1f, 2.2f, 3.3f]; 131 auto m = Model!d(); 132 133 auto visitor = PrettyPrintingVisitor(9); 134 visitor.processItem; 135 m.collapsed = false; 136 m.visitForward(d, visitor); 137 138 visitor.output ~= '\0'; 139 version(none) 140 { 141 import core.stdc.stdio : printf; 142 printf("%s\nlength: %ld\n", visitor.output[].ptr, visitor.output.length); 143 } 144 145 import std.algorithm : equal; 146 assert(visitor.output[].equal(" 147 Caption: float[3] 148 1.100000 149 2.200000 150 3.300000 151 \0" 152 )); 153 } 154 155 version(unittest) @Name("dynamic_array") 156 unittest 157 { 158 float[] d = [1.1f, 2.2f, 3.3f]; 159 auto m = Model!d(); 160 161 auto visitor = PrettyPrintingVisitor(9); 162 visitor.processItem; 163 m.collapsed = false; 164 m.model.length = d.length; 165 m.visitForward(d, visitor); 166 167 d ~= [4.4f, 5.5f]; 168 m.model.length = d.length; 169 m.visitForward(d, visitor); 170 171 d = d[2..3]; 172 m.model.length = d.length; 173 m.visitForward(d, visitor); 174 175 visitor.output ~= '\0'; 176 version(none) 177 { 178 import core.stdc.stdio : printf; 179 printf("%s\nlength: %ld\n", visitor.output[].ptr, visitor.output.length); 180 } 181 182 import std.algorithm : equal; 183 assert(visitor.output[].equal(" 184 Caption: float[] 185 1.100000 186 2.200000 187 3.300000 188 Caption: float[] 189 1.100000 190 2.200000 191 3.300000 192 4.400000 193 5.500000 194 Caption: float[] 195 3.300000 196 \0" 197 )); 198 } 199 200 version(unittest) @Name("aggregate_with_only_member") 201 @nogc unittest 202 { 203 static struct OneMember 204 { 205 string one = "one"; 206 } 207 208 auto d = OneMember(); 209 auto m = Model!d(); 210 import std.traits : FieldNameTuple; 211 import std.meta : AliasSeq; 212 static assert(FieldNameTuple!(typeof(m)) == AliasSeq!("single_member_model")); 213 214 auto visitor = PrettyPrintingVisitor(9); 215 visitor.processItem; 216 m.visitForward(d, visitor); 217 218 visitor.output ~= '\0'; 219 220 version(none) 221 { 222 import core.stdc.stdio : printf; 223 printf("---\n%s\n---\nlength: %ld\n", 224 visitor.output[].ptr, visitor.output.length); 225 } 226 227 import std.algorithm : equal; 228 assert(visitor.output[].equal(" 229 one 230 \0" 231 )); 232 } 233 234 version(unittest) @Name("aggregate_with_render_header") 235 unittest 236 { 237 static struct Aggregate 238 { 239 float f; 240 long l; 241 242 void renderHeader(W)(ref W writer) @trusted const 243 { 244 import core.stdc.stdio : snprintf; 245 246 char[128] buffer; 247 const l = snprintf(&buffer[0], buffer.length, "Custom header %f, %ld", f, l); 248 writer.put(buffer[0..l]); 249 } 250 } 251 252 import auxil.traits : hasRenderHeader; 253 static assert(hasRenderHeader!Aggregate); 254 255 auto d = Aggregate(); 256 257 static assert(hasRenderHeader!d); 258 auto m = Model!d(); 259 m.collapsed = false; 260 261 auto visitor = PrettyPrintingVisitor(9); 262 visitor.processItem; 263 m.visitForward(d, visitor); 264 265 visitor.output ~= '\0'; 266 267 version(none) 268 { 269 import core.stdc.stdio : printf; 270 printf("---\n%s\n---\nlength: %ld\n", 271 visitor.output[].ptr, visitor.output.length); 272 } 273 274 import std.algorithm : equal; 275 assert(visitor.output[].equal(" 276 Custom header nan, 0 277 nan 278 0 279 \0" 280 )); 281 } 282 283 version(unittest) 284 { 285 import auxil.traits : renderedAs, renderedAsMember; 286 287 private struct Proxy 288 { 289 float f; 290 291 this(ref const(Test3) t3) 292 { 293 f = t3.f; 294 } 295 } 296 297 @renderedAs!Proxy 298 private struct Test3 299 { 300 int i; 301 float f; 302 } 303 304 @renderedAs!int 305 private struct Test4 306 { 307 int i; 308 float f; 309 310 int opCast(T : int)() const 311 { 312 return i; 313 } 314 } 315 316 @renderedAsMember!"t4.i" 317 private struct Test5 318 { 319 Test4 t4; 320 float f; 321 } 322 } 323 324 version(unittest) @Name("aggregate_proxy") 325 unittest 326 { 327 import auxil.traits :hasRenderedAs; 328 { 329 auto test3 = Test3(123, 123.0f); 330 331 auto d = Proxy(test3); 332 auto m = makeModel(d); 333 334 static assert( hasRenderedAs!Test3); 335 static assert(!hasRenderedAs!test3); 336 import std.traits : FieldNameTuple; 337 import std.meta : AliasSeq; 338 static assert(FieldNameTuple!(typeof(m)) == AliasSeq!("single_member_model")); 339 340 auto visitor = PrettyPrintingVisitor(9); 341 visitor.processItem; 342 m.visitForward(d, visitor); 343 344 visitor.output ~= '\0'; 345 346 version(none) 347 { 348 import core.stdc.stdio : printf; 349 printf("---\n%s\n---\nlength: %ld\n", 350 visitor.output[].ptr, visitor.output.length); 351 } 352 353 import std.algorithm : equal; 354 assert(visitor.output[].equal(" 355 123.000000 356 \0" 357 )); 358 } 359 360 { 361 auto d = Test3(); 362 auto m = makeModel(d); 363 static assert(!m.Collapsable); 364 365 auto visitor = PrettyPrintingVisitor(9); 366 visitor.processItem; 367 m.visitForward(d, visitor); 368 369 visitor.output ~= '\0'; 370 371 version(none) 372 { 373 import core.stdc.stdio : printf; 374 printf("---\n%s\n---\nlength: %ld\n", 375 visitor.output[].ptr, visitor.output.length); 376 } 377 378 import std.algorithm : equal; 379 assert(visitor.output[].equal(" 380 nan 381 \0" 382 )); 383 } 384 385 { 386 Test4 test4; 387 test4.i = 112; 388 auto m = makeModel(test4); 389 390 static assert(is(m.Proxy)); 391 static assert(is(typeof(m.proxy) == int)); 392 assert(m.proxy == 112); 393 } 394 395 { 396 auto d = Test5(Test4(11, 22.2)); 397 auto m = makeModel(d); 398 static assert(!m.Collapsable); 399 400 auto visitor = PrettyPrintingVisitor(9); 401 visitor.processItem; 402 m.visitForward(d, visitor); 403 404 visitor.output ~= '\0'; 405 406 version(none) 407 { 408 import core.stdc.stdio : printf; 409 printf("---\n%s\n---\nlength: %ld\n", 410 visitor.output[].ptr, visitor.output.length); 411 } 412 413 import std.algorithm : equal; 414 assert(visitor.output[].equal(" 415 11 416 \0" 417 )); 418 } 419 420 { 421 @("wrongAttribute") 422 @("renderedAsMember.f", "123") 423 @("123") 424 static struct Test 425 { 426 int i; 427 float f; 428 } 429 auto d = Test(11, 1.0); 430 auto m = makeModel(d); 431 static assert(!m.Collapsable); 432 433 auto visitor = PrettyPrintingVisitor(9); 434 visitor.processItem; 435 m.visitForward(d, visitor); 436 437 visitor.output ~= '\0'; 438 439 version(none) 440 { 441 import core.stdc.stdio : printf; 442 printf("---\n%s\n---\nlength: %ld\n", 443 visitor.output[].ptr, visitor.output.length); 444 } 445 446 import std.algorithm : equal; 447 assert(visitor.output[].equal(" 448 1.000000 449 \0" 450 )); 451 } 452 } 453 454 version(unittest) @Name("TaggedAlgebraic") 455 unittest 456 { 457 import taggedalgebraic : TaggedAlgebraic, Void, get; 458 import unit_threaded : should, be; 459 460 static struct Struct 461 { 462 double d; 463 char c; 464 } 465 466 struct Payload 467 { 468 Void v; 469 float f; 470 int i; 471 string sg; 472 Struct st; 473 string[] sa; 474 double[] da; 475 } 476 477 alias Data = TaggedAlgebraic!Payload; 478 479 Data[] data = [ 480 Data(1.2f), 481 Data(4), 482 Data("string"), 483 Data(Struct(100, 'd')), 484 Data(["str0", "str1", "str2"]), 485 Data([0.1, 0.2, 0.3]), 486 ]; 487 488 auto model = makeModel(data); 489 assert(model.length == data.length); 490 assert(model[4].get!(Model!(string[])).length == data[4].length); 491 492 // default value 493 model.collapsed.should.be == true; 494 495 // change it directly 496 model.collapsed = false; 497 model.collapsed.should.be == false; 498 499 // change the value of the root by tree path 500 setPropertyByTreePath!"collapsed"(data, model, [], true); 501 model.collapsed.should.be == true; 502 { 503 const r = getPropertyByTreePath!("collapsed", bool)(data, model, []); 504 r.isNull.should.be == false; 505 r.get.should.be == true; 506 } 507 508 // change the root once again 509 setPropertyByTreePath!"collapsed"(data, model, [], false); 510 model.collapsed.should.be == false; 511 512 // the value of the child by tree path 513 setPropertyByTreePath!"collapsed"(data, model, [3], false); 514 model.model[3].collapsed.should.be == false; 515 516 foreach(ref e; model.model) 517 e.collapsed = false; 518 519 auto visitor = PrettyPrintingVisitor(9); 520 visitor.processItem; 521 model.visitForward(data, visitor); 522 523 data[4] ~= "recently added 4th element"; 524 model[4].update(data[4]); 525 model.visitForward(data, visitor); 526 527 data[4] = data[4].get!(string[])[3..$]; 528 data[4].get!(string[])[0] = "former 4th element, now the only one"; 529 model[4].update(data[4]); 530 model.visitForward(data, visitor); 531 532 visitor.output ~= '\0'; 533 version(none) 534 { 535 import core.stdc.stdio : printf; 536 printf("%s\nlength: %ld\n", visitor.output[].ptr, visitor.output.length); 537 } 538 539 import std.algorithm : equal; 540 assert(visitor.output[].equal(" 541 Caption: TaggedAlgebraic!(Payload)[] 542 1.200000 543 4 544 string 545 Caption: Struct 546 100.000000 547 d 548 Caption: string[] 549 str0 550 str1 551 str2 552 Caption: double[] 553 0.100000 554 0.200000 555 0.300000 556 Caption: TaggedAlgebraic!(Payload)[] 557 1.200000 558 4 559 string 560 Caption: Struct 561 100.000000 562 d 563 Caption: string[] 564 str0 565 str1 566 str2 567 recently added 4th element 568 Caption: double[] 569 0.100000 570 0.200000 571 0.300000 572 Caption: TaggedAlgebraic!(Payload)[] 573 1.200000 574 4 575 string 576 Caption: Struct 577 100.000000 578 d 579 Caption: string[] 580 former 4th element, now the only one 581 Caption: double[] 582 0.100000 583 0.200000 584 0.300000 585 \0" 586 )); 587 } 588 589 version(unittest) @Name("nogc_dynamic_array") 590 @nogc 591 unittest 592 { 593 import std.experimental.allocator.mallocator : Mallocator; 594 import automem.vector : Vector; 595 Vector!(float, Mallocator) data; 596 data ~= 0.1f; 597 data ~= 0.2f; 598 data ~= 0.3f; 599 600 auto model = makeModel(data[]); 601 602 auto visitor = PrettyPrintingVisitor(14); 603 visitor.processItem; 604 model.visitForward(data[], visitor); 605 assert(model.size == visitor.size + model.Spacing); 606 607 model.collapsed = false; 608 model.visitForward(data[], visitor); 609 610 assert(model.size == 4*(visitor.size + model.Spacing)); 611 foreach(e; model.model) 612 assert(e.size == (visitor.size + model.Spacing)); 613 614 visitor.output ~= '\0'; 615 version(none) 616 { 617 import core.stdc.stdio : printf; 618 printf("%s\nlength: %ld\n", visitor.output[].ptr, visitor.output.length); 619 } 620 621 import std.algorithm : equal; 622 assert(visitor.output[].equal(" 623 Caption: float[] 624 Caption: float[] 625 0.100000 626 0.200000 627 0.300000 628 \0" 629 )); 630 } 631 632 version(unittest) @Name("size_measuring") 633 unittest 634 { 635 import taggedalgebraic : TaggedAlgebraic, Void, get; 636 import unit_threaded : should, be; 637 638 static struct Struct 639 { 640 double d; 641 char c; 642 } 643 644 struct Payload 645 { 646 Void v; 647 float f; 648 int i; 649 string sg; 650 Struct st; 651 string[] sa; 652 double[] da; 653 } 654 655 alias Data = TaggedAlgebraic!Payload; 656 657 Data[] data = [ 658 Data(1.2f), 659 Data(4), 660 Data("string"), 661 Data(Struct(100, 'd')), 662 Data(["str0", "str1", "str2"]), 663 Data([0.1, 0.2, 0.3]), 664 ]; 665 666 auto model = makeModel(data); 667 assert(model.length == data.length); 668 assert(model[4].get!(Model!(string[])).length == data[4].length); 669 670 model.size.should.be == 0; 671 auto visitor = PrettyPrintingVisitor(17); 672 model.visitForward(data, visitor); 673 674 model.collapsed.should.be == true; 675 model.size.should.be ~ (visitor.size + model.Spacing); 676 model.size.should.be ~ 18.0; 677 visitor.position.should.be ~ 0.0; 678 679 setPropertyByTreePath!"collapsed"(data, model, [], false); 680 model.visitForward(data, visitor); 681 model.size.should.be ~ (visitor.size + model.Spacing)*7; 682 model.size.should.be ~ 18.0*7; 683 visitor.position.should.be ~ 6*18.0; 684 685 setPropertyByTreePath!"collapsed"(data, model, [3], false); 686 model.visitForward(data, visitor); 687 model.size.should.be ~ (visitor.size + model.Spacing)*9; 688 model.size.should.be ~ 18.0*9; 689 visitor.position.should.be ~ (6+2)*18.0; 690 691 setPropertyByTreePath!"collapsed"(data, model, [4], false); 692 model.visitForward(data, visitor); 693 model.size.should.be ~ (visitor.size + model.Spacing)*12; 694 model.size.should.be ~ 18.0*12; 695 visitor.position.should.be ~ (6+2+3)*18.0; 696 697 setPropertyByTreePath!"collapsed"(data, model, [5], false); 698 model.visitForward(data, visitor); 699 model.size.should.be ~ (visitor.size + model.Spacing)*15; 700 model.size.should.be ~ 18.0*15; 701 visitor.position.should.be ~ (6+2+3+3)*18.0; 702 703 visitor.destination = visitor.destination.nan; 704 model.visitForward(data, visitor); 705 model.size.should.be == 270; 706 visitor.position.should.be == 252; 707 708 visitor.position = 0; 709 visitor.destination = 100; 710 model.visitForward(data, visitor); 711 model.size.should.be == 126; 712 visitor.position.should.be == 90; 713 } 714 715 struct RelativeMeasurer 716 { 717 DefaultVisitorImpl!(SizeEnabled.no, TreePathEnabled.yes) default_visitor; 718 alias default_visitor this; 719 720 TreePosition[] output; 721 722 void enterTree(Order order, Data, Model)(ref const(Data) data, ref Model model) 723 { 724 output = null; 725 } 726 727 void enterNode(Order order, Data, Model)(ref const(Data) data, ref Model model) 728 { 729 static if (order == Order.Sinking) 730 output ~= TreePosition(tree_path.value, position); 731 } 732 733 void leaveNode(Order order, Data, Model)(ref const(Data) data, ref Model model) 734 { 735 static if (order == Order.Bubbling) 736 output ~= TreePosition(tree_path.value, position); 737 } 738 739 void processLeaf(Order order, Data, Model)(ref const(Data) data, ref Model model) 740 { 741 output ~= TreePosition(tree_path.value, position); 742 } 743 } 744 745 struct TreePosition 746 { 747 import std.experimental.allocator.mallocator : Mallocator; 748 import automem : Vector; 749 Vector!(int, Mallocator) path; 750 double size; 751 752 @disable this(); 753 754 this(P, S)(P p, S s) 755 if (!is(P == void[])) 756 { 757 path = p; 758 size = s; 759 } 760 761 this(S)(void[] p, S s) 762 { 763 size = s; 764 } 765 766 import std.range : isOutputRange; 767 import std.format : FormatSpec; 768 769 void toString(Writer) (ref Writer w, scope const ref FormatSpec!char fmt) const 770 if (isOutputRange!(Writer, char)) 771 { 772 import std.algorithm : copy; 773 import std.conv : text; 774 775 copy(typeof(this).stringof, w); 776 w.put('('); 777 copy(text(path[], ", ", size), w); 778 w.put(')'); 779 } 780 } 781 782 version(unittest) 783 { 784 import unit_threaded; 785 786 class Fixture : TestCase 787 { 788 import unit_threaded : should, be; 789 import taggedalgebraic : TaggedAlgebraic; 790 791 override void setup() 792 { 793 data = [ 794 Data(1), 795 Data(2.0), 796 Data(3.0f), 797 Data(Test(100, -1001)), 798 Data(Test2(1_000_000, "test2", Test(200, -11), [11, 12, 123])), 799 Data("text"), 800 ]; 801 802 v = RelativeMeasurer(); 803 model = makeModel(data); 804 model.collapsed = false; 805 setPropertyByTreePath!"collapsed"(data, model, [3], false); 806 setPropertyByTreePath!"collapsed"(data, model, [4], false); 807 setPropertyByTreePath!"collapsed"(data, model, [4, 2], false); 808 setPropertyByTreePath!"collapsed"(data, model, [4, 3], false); 809 810 // measure size 811 { 812 auto mv = MeasuringVisitor(9); 813 model.visitForward(data, mv); 814 } 815 } 816 817 package: 818 819 static struct Test 820 { 821 ushort us; 822 long l; 823 } 824 825 static struct Test2 826 { 827 size_t st; 828 string s; 829 Test t; 830 float[] fa; 831 } 832 833 static union Payload 834 { 835 int i; 836 float f; 837 double d; 838 string str; 839 Test t; 840 Test2 t2; 841 } 842 843 alias Data = TaggedAlgebraic!Payload; 844 845 Data[] data; 846 RelativeMeasurer v; 847 typeof(makeModel(data)) model; 848 } 849 850 class Test1 : Fixture 851 { 852 override void test() 853 { 854 v.position = 0; 855 model.visitForward(data, v); 856 model.size.should.be == 180; 857 v.output.should.be == [ 858 TreePosition([ ], 0), 859 TreePosition([0], 10), 860 TreePosition([1], 20), 861 TreePosition([2], 30), 862 TreePosition([3], 40), 863 TreePosition([3, 0], 50), 864 TreePosition([3, 1], 60), 865 TreePosition([4], 70), 866 TreePosition([4, 0], 80), 867 TreePosition([4, 1], 90), 868 TreePosition([4, 2], 100), 869 TreePosition([4, 2, 0], 110), 870 TreePosition([4, 2, 1], 120), 871 TreePosition([4, 3], 130), 872 TreePosition([4, 3, 0], 140), 873 TreePosition([4, 3, 1], 150), 874 TreePosition([4, 3, 2], 160), 875 TreePosition([5], 170), 876 ]; 877 v.position.should.be == 170; 878 879 v.position = 0; 880 v.path.value = [4,2,1]; 881 model.visitForward(data, v); 882 v.output.should.be == [ 883 TreePosition([4, 2, 1], 0), 884 TreePosition([4, 3], 10), 885 TreePosition([4, 3, 0], 20), 886 TreePosition([4, 3, 1], 30), 887 TreePosition([4, 3, 2], 40), 888 TreePosition([5], 50) 889 ]; 890 } 891 } 892 893 class Test2 : Fixture 894 { 895 override void test() 896 { 897 // default 898 { 899 v.path.clear; 900 v.position = 0; 901 v.destination = v.destination.nan; 902 model.visitForward(data, v); 903 904 v.position.should.be == 170; 905 v.path.value[].should.be == []; 906 } 907 908 // next position is between two elements 909 { 910 v.path.clear; 911 v.position = 0; 912 v.destination = 15; 913 model.visitForward(data, v); 914 915 v.position.should.be == 10; 916 v.destination.should.be == 15; 917 v.path.value[].should.be == [0]; 918 } 919 920 // next position is equal to start of an element 921 { 922 v.path.clear; 923 v.position = 0; 924 v.destination = 30; 925 model.visitForward(data, v); 926 927 v.position.should.be == 30; 928 v.destination.should.be == 30; 929 v.path.value[].should.be == [2]; 930 } 931 932 // start path is not null 933 { 934 v.path.value = [3, 0]; 935 v.position = 0; 936 v.destination = 55; 937 model.visitForward(data, v); 938 939 v.position.should.be == 50; 940 v.destination.should.be == 55; 941 v.path.value[].should.be == [4, 2]; 942 } 943 944 // reverse order, start path is not null 945 { 946 v.path.value = [4, 1]; 947 v.position = 90; 948 v.destination = 41; 949 950 model.visitBackward(data, v); 951 952 v.position.should.be == 40; 953 v.destination.should.be == 41; 954 v.path.value[].should.be == [3]; 955 956 // bubble to the next element 957 v.destination = 19; 958 959 model.visitBackward(data, v); 960 961 v.path.value[].should.be == [0]; 962 v.position.should.be == 10; 963 v.destination.should.be == 19; 964 v.output.should.be == [ 965 TreePosition([3], 40), 966 TreePosition([2], 30), 967 TreePosition([1], 20), 968 TreePosition([0], 10), 969 ]; 970 } 971 } 972 } 973 974 class ScrollingTest : Fixture 975 { 976 override void test() 977 { 978 v.path.clear; 979 v.position = 0; 980 981 // the element height is 10 px 982 983 // scroll 7 px forward 984 visit(model, data, v, 7); 985 // current element is the root one 986 v.path.value[].should.be == []; 987 // position of the current element is 0 px 988 v.position.should.be == 0; 989 // the window starts from 7th px 990 v.destination.should.be == 7; 991 992 // scroll the next 7th px forward 993 visit(model, data, v, 14); 994 // the current element is the first child element 995 v.path.value[].should.be == [0]; 996 // position of the current element is 10 px 997 v.position.should.be == 10; 998 // the window starts from 14th px 999 v.destination.should.be == 14; 1000 1001 // scroll the next 7th px forward 1002 visit(model, data, v, 21); 1003 // the current element is the second child element 1004 v.path.value[].should.be == [1]; 1005 // position of the current element is 20 px 1006 v.position.should.be == 20; 1007 // the window starts from 21th px 1008 v.destination.should.be == 21; 1009 1010 // scroll the next 7th px forward 1011 visit(model, data, v, 28); 1012 // the current element is the second child element 1013 v.path.value[].should.be == [1]; 1014 // position of the current element is 20 px 1015 v.position.should.be == 20; 1016 // the window starts from 28th px 1017 v.destination.should.be == 28; 1018 1019 // scroll the next 7th px forward 1020 visit(model, data, v, 35); 1021 // the current element is the third child element 1022 v.path.value[].should.be == [2]; 1023 // position of the current element is 30 px 1024 v.position.should.be == 30; 1025 // the window starts from 35th px 1026 v.destination.should.be == 35; 1027 1028 // scroll 7th px backward 1029 visit(model, data, v, 27); 1030 // the current element is the second child element 1031 v.path.value[].should.be == [1]; 1032 // position of the current element is 20 px 1033 v.position.should.be == 20; 1034 // the window starts from 27th px 1035 v.destination.should.be == 27; 1036 1037 // scroll the next 9th px backward 1038 visit(model, data, v, 18); 1039 // the current element is the first child element 1040 v.path.value[].should.be == [0]; 1041 // position of the current element is 10 px 1042 v.position.should.be == 10; 1043 // the window starts from 18th px 1044 v.destination.should.be == 18; 1045 1046 // scroll the next 6th px backward 1047 visit(model, data, v, 12); 1048 // the current element is the first child element 1049 v.path.value[].should.be == [0]; 1050 // position of the current element is 10 px 1051 v.position.should.be == 10; 1052 // the window starts from 12th px 1053 v.destination.should.be == 12; 1054 1055 // scroll the next 5th px backward 1056 visit(model, data, v, 7); 1057 // the current element is the root element 1058 v.path.value[].should.be == []; 1059 // position of the current element is 0 px 1060 v.position.should.be == 0; 1061 // the window starts from 7th px 1062 v.destination.should.be == 7; 1063 1064 // scroll 76 px forward 1065 visit(model, data, v, 83); 1066 // // the current element is the second child element 1067 // v.path.value[].should.be == [4, 0]; 1068 // // position of the current element is 20 px 1069 // v.position.should.be == 80; 1070 // the window starts from 27th px 1071 v.destination.should.be == 83; 1072 1073 visit(model, data, v, 81); 1074 v.path.value[].should.be == [4, 0]; 1075 v.position.should.be == 80; 1076 v.destination.should.be == 81; 1077 1078 visit(model, data, v, 80); 1079 v.path.value[].should.be == [4, 0]; 1080 v.position.should.be == 80; 1081 v.destination.should.be == 80; 1082 1083 visit(model, data, v, 79.1); 1084 v.path.value[].should.be == [4]; 1085 v.position.should.be == 70; 1086 v.destination.should.be ~ 79.1; 1087 1088 visit(model, data, v, 133.4); 1089 v.path.value[].should.be == [4, 3]; 1090 v.position.should.be == 130; 1091 v.destination.should.be ~ 133.4; 1092 1093 visit(model, data, v, 0); 1094 v.path.value[].should.be == []; 1095 v.position.should.be == 0; 1096 v.destination.should.be ~ 0.0; 1097 } 1098 } 1099 } 1100 1101 version(unittest) @Name("reverse_dynamic_array") 1102 unittest 1103 { 1104 import unit_threaded : should, be; 1105 1106 auto data = [0, 1, 2, 3]; 1107 auto model = makeModel(data); 1108 auto visitor = RelativeMeasurer(); 1109 1110 model.collapsed = false; 1111 { 1112 auto mv = MeasuringVisitor(9); 1113 model.visitForward(data, mv); 1114 } 1115 visitor.position = 0; 1116 model.visitForward(data, visitor); 1117 visitor.output.should.be == [ 1118 TreePosition([ ], 0), 1119 TreePosition([0], 10), 1120 TreePosition([1], 20), 1121 TreePosition([2], 30), 1122 TreePosition([3], 40), 1123 ]; 1124 1125 visitor.position.should.be == 40; 1126 1127 model.visitBackward(data, visitor); 1128 visitor.output.should.be == [ 1129 TreePosition([3], 40), 1130 TreePosition([2], 30), 1131 TreePosition([1], 20), 1132 TreePosition([0], 10), 1133 TreePosition([ ], 0), 1134 ]; 1135 1136 visitor.path.value = [1,]; 1137 visitor.position = 20; 1138 model.visitForward(data, visitor); 1139 visitor.output.should.be == [ 1140 TreePosition([1], 20), 1141 TreePosition([2], 30), 1142 TreePosition([3], 40), 1143 ]; 1144 visitor.position = 20; 1145 model.visitBackward(data, visitor); 1146 visitor.output.should.be == [ 1147 TreePosition([1], 20), 1148 TreePosition([0], 10), 1149 TreePosition([ ], 0), 1150 ]; 1151 } 1152 1153 version(unittest) @Name("aggregate") 1154 unittest 1155 { 1156 import unit_threaded : should, be; 1157 1158 static struct NestedData1 1159 { 1160 long l; 1161 char ch; 1162 } 1163 1164 static struct NestedData2 1165 { 1166 short sh; 1167 NestedData1 data1; 1168 string str; 1169 } 1170 1171 static struct Data 1172 { 1173 int i; 1174 float f; 1175 double d; 1176 string s; 1177 NestedData2 data2; 1178 } 1179 1180 const data = Data(0, 1, 2, "3", NestedData2(ushort(2), NestedData1(1_000_000_000, 'z'), "text")); 1181 auto model = makeModel(data); 1182 auto visitor = RelativeMeasurer(); 1183 1184 model.collapsed = false; 1185 { 1186 auto mv = MeasuringVisitor(9); 1187 model.visitForward(data, mv); 1188 } 1189 visitor.position = 0; 1190 model.visitForward(data, visitor); 1191 visitor.output.should.be == [ 1192 TreePosition([], 0), 1193 TreePosition([0], 10), 1194 TreePosition([1], 20), 1195 TreePosition([2], 30), 1196 TreePosition([3], 40), 1197 TreePosition([4], 50), 1198 ]; 1199 visitor.position.should.be == 50; 1200 1201 { 1202 visitor.path.clear; 1203 visitor.position = 0; 1204 visitor.destination = 30; 1205 model.visitForward(data, visitor); 1206 visitor.output.should.be == [ 1207 TreePosition([], 0), 1208 TreePosition([0], 10), 1209 TreePosition([1], 20), 1210 TreePosition([2], 30), 1211 ]; 1212 visitor.position.should.be == 30; 1213 } 1214 1215 { 1216 visitor.path.clear; 1217 visitor.position = 30; 1218 visitor.destination = visitor.position + 30; 1219 model.visitForward(data, visitor); 1220 visitor.output.should.be == [ 1221 TreePosition([], 30), 1222 TreePosition([0], 40), 1223 TreePosition([1], 50), 1224 TreePosition([2], 60), 1225 ]; 1226 visitor.position.should.be == 60; 1227 } 1228 1229 { 1230 visitor.path.value = [0]; 1231 visitor.position = 130; 1232 visitor.destination = visitor.position + 20; 1233 model.visitForward(data, visitor); 1234 visitor.output.should.be == [ 1235 TreePosition([0], 130), 1236 TreePosition([1], 140), 1237 TreePosition([2], 150), 1238 ]; 1239 visitor.position.should.be == 150; 1240 } 1241 1242 visitor.path.value = [2]; 1243 visitor.position = 30; 1244 visitor.destination = visitor.destination.nan; 1245 model.visitForward(data, visitor); 1246 1247 visitor.output.should.be == [ 1248 TreePosition([2], 30), 1249 TreePosition([3], 40), 1250 TreePosition([4], 50), 1251 ]; 1252 1253 visitor.path.clear; 1254 visitor.destination = visitor.destination.nan; 1255 model.visitBackward(data, visitor); 1256 visitor.output.should.be == [ 1257 TreePosition([4], 50), 1258 TreePosition([3], 40), 1259 TreePosition([2], 30), 1260 TreePosition([1], 20), 1261 TreePosition([0], 10), 1262 TreePosition([], 0), 1263 ]; 1264 } 1265 1266 version(unittest) @Name("new_paradigm") 1267 unittest 1268 { 1269 static struct TrivialStruct 1270 { 1271 int i; 1272 float f; 1273 } 1274 1275 static struct StructNullable 1276 { 1277 import std.typecons : Nullable; 1278 int i; 1279 Nullable!float f; 1280 } 1281 1282 auto d = StructNullable(); 1283 auto m = makeModel(d); 1284 m.collapsed = false; 1285 auto visitor = DefaultVisitor(19); 1286 m.visitForward(d, visitor); 1287 import std; 1288 writeln(m); 1289 }