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