| skipped 91 lines |
92 | 92 | | wantContainerErr: true, |
93 | 93 | | }, |
94 | 94 | | { |
| 95 | + | desc: "fails on invalid option on the first vertical child container", |
| 96 | + | termSize: image.Point{10, 10}, |
| 97 | + | container: func(ft *faketerm.Terminal) (*Container, error) { |
| 98 | + | return New( |
| 99 | + | ft, |
| 100 | + | SplitVertical( |
| 101 | + | Left( |
| 102 | + | MarginTop(-1), |
| 103 | + | ), |
| 104 | + | Right(), |
| 105 | + | ), |
| 106 | + | ) |
| 107 | + | }, |
| 108 | + | wantContainerErr: true, |
| 109 | + | }, |
| 110 | + | { |
| 111 | + | desc: "fails on invalid option on the second vertical child container", |
| 112 | + | termSize: image.Point{10, 10}, |
| 113 | + | container: func(ft *faketerm.Terminal) (*Container, error) { |
| 114 | + | return New( |
| 115 | + | ft, |
| 116 | + | SplitVertical( |
| 117 | + | Left(), |
| 118 | + | Right( |
| 119 | + | MarginTop(-1), |
| 120 | + | ), |
| 121 | + | ), |
| 122 | + | ) |
| 123 | + | }, |
| 124 | + | wantContainerErr: true, |
| 125 | + | }, |
| 126 | + | { |
| 127 | + | desc: "fails on invalid option on the first horizontal child container", |
| 128 | + | termSize: image.Point{10, 10}, |
| 129 | + | container: func(ft *faketerm.Terminal) (*Container, error) { |
| 130 | + | return New( |
| 131 | + | ft, |
| 132 | + | SplitHorizontal( |
| 133 | + | Top( |
| 134 | + | MarginTop(-1), |
| 135 | + | ), |
| 136 | + | Bottom(), |
| 137 | + | ), |
| 138 | + | ) |
| 139 | + | }, |
| 140 | + | wantContainerErr: true, |
| 141 | + | }, |
| 142 | + | { |
| 143 | + | desc: "fails on invalid option on the second horizontal child container", |
| 144 | + | termSize: image.Point{10, 10}, |
| 145 | + | container: func(ft *faketerm.Terminal) (*Container, error) { |
| 146 | + | return New( |
| 147 | + | ft, |
| 148 | + | SplitHorizontal( |
| 149 | + | Top(), |
| 150 | + | Bottom( |
| 151 | + | MarginTop(-1), |
| 152 | + | ), |
| 153 | + | ), |
| 154 | + | ) |
| 155 | + | }, |
| 156 | + | wantContainerErr: true, |
| 157 | + | }, |
| 158 | + | { |
95 | 159 | | desc: "fails on MarginTopPercent too low", |
96 | 160 | | termSize: image.Point{10, 10}, |
97 | 161 | | container: func(ft *faketerm.Terminal) (*Container, error) { |
| skipped 302 lines |
400 | 464 | | termSize: image.Point{10, 10}, |
401 | 465 | | container: func(ft *faketerm.Terminal) (*Container, error) { |
402 | 466 | | return New(ft, PaddingLeftPercent(1), PaddingLeft(1)) |
| 467 | + | }, |
| 468 | + | wantContainerErr: true, |
| 469 | + | }, |
| 470 | + | { |
| 471 | + | desc: "fails on empty ID specified", |
| 472 | + | termSize: image.Point{10, 10}, |
| 473 | + | container: func(ft *faketerm.Terminal) (*Container, error) { |
| 474 | + | return New(ft, ID("")) |
| 475 | + | }, |
| 476 | + | wantContainerErr: true, |
| 477 | + | }, |
| 478 | + | { |
| 479 | + | desc: "fails on empty duplicate ID specified", |
| 480 | + | termSize: image.Point{10, 10}, |
| 481 | + | container: func(ft *faketerm.Terminal) (*Container, error) { |
| 482 | + | return New( |
| 483 | + | ft, |
| 484 | + | ID("0"), |
| 485 | + | SplitHorizontal( |
| 486 | + | Top(ID("1")), |
| 487 | + | Bottom(ID("1")), |
| 488 | + | ), |
| 489 | + | ) |
403 | 490 | | }, |
404 | 491 | | wantContainerErr: true, |
405 | 492 | | }, |
| skipped 436 lines |
842 | 929 | | if err != nil { |
843 | 930 | | return |
844 | 931 | | } |
| 932 | + | contStr := cont.String() |
| 933 | + | t.Logf("For container: %v", contStr) |
845 | 934 | | if err := cont.Draw(); err != nil { |
846 | 935 | | t.Fatalf("Draw => unexpected error: %v", err) |
847 | 936 | | } |
| skipped 16 lines |
864 | 953 | | |
865 | 954 | | } |
866 | 955 | | |
867 | | - | // eventGroup is a group of events to be delivered with synchronization. |
868 | | - | // I.e. the test execution waits until the specified number is processed before |
869 | | - | // proceeding with test execution. |
870 | | - | type eventGroup struct { |
871 | | - | events []terminalapi.Event |
872 | | - | wantProcessed int |
873 | | - | } |
874 | | - | |
875 | 956 | | // errorHandler just stores the last error received. |
876 | 957 | | type errorHandler struct { |
877 | 958 | | err error |
| skipped 14 lines |
892 | 973 | | |
893 | 974 | | func TestKeyboard(t *testing.T) { |
894 | 975 | | tests := []struct { |
895 | | - | desc string |
896 | | - | termSize image.Point |
897 | | - | container func(ft *faketerm.Terminal) (*Container, error) |
898 | | - | eventGroups []*eventGroup |
899 | | - | want func(size image.Point) *faketerm.Terminal |
900 | | - | wantErr bool |
| 976 | + | desc string |
| 977 | + | termSize image.Point |
| 978 | + | container func(ft *faketerm.Terminal) (*Container, error) |
| 979 | + | events []terminalapi.Event |
| 980 | + | // If specified, waits for this number of events. |
| 981 | + | // Otherwise waits for len(events). |
| 982 | + | wantProcessed int |
| 983 | + | want func(size image.Point) *faketerm.Terminal |
| 984 | + | wantErr bool |
901 | 985 | | }{ |
902 | 986 | | { |
903 | 987 | | desc: "event not forwarded if container has no widget", |
| skipped 1 lines |
905 | 989 | | container: func(ft *faketerm.Terminal) (*Container, error) { |
906 | 990 | | return New(ft) |
907 | 991 | | }, |
908 | | - | eventGroups: []*eventGroup{ |
909 | | - | { |
910 | | - | events: []terminalapi.Event{ |
911 | | - | &terminalapi.Keyboard{Key: keyboard.KeyEnter}, |
912 | | - | }, |
913 | | - | wantProcessed: 0, |
914 | | - | }, |
| 992 | + | events: []terminalapi.Event{ |
| 993 | + | &terminalapi.Keyboard{Key: keyboard.KeyEnter}, |
915 | 994 | | }, |
916 | 995 | | want: func(size image.Point) *faketerm.Terminal { |
917 | 996 | | return faketerm.MustNew(size) |
| skipped 22 lines |
940 | 1019 | | ), |
941 | 1020 | | ) |
942 | 1021 | | }, |
943 | | - | eventGroups: []*eventGroup{ |
| 1022 | + | events: []terminalapi.Event{ |
944 | 1023 | | // Move focus to the target container. |
945 | | - | { |
946 | | - | events: []terminalapi.Event{ |
947 | | - | &terminalapi.Mouse{Position: image.Point{39, 19}, Button: mouse.ButtonLeft}, |
948 | | - | &terminalapi.Mouse{Position: image.Point{39, 19}, Button: mouse.ButtonRelease}, |
949 | | - | }, |
950 | | - | wantProcessed: 2, |
951 | | - | }, |
| 1024 | + | &terminalapi.Mouse{Position: image.Point{39, 19}, Button: mouse.ButtonLeft}, |
| 1025 | + | &terminalapi.Mouse{Position: image.Point{39, 19}, Button: mouse.ButtonRelease}, |
952 | 1026 | | // Send the keyboard event. |
953 | | - | { |
954 | | - | events: []terminalapi.Event{ |
955 | | - | &terminalapi.Keyboard{Key: keyboard.KeyEnter}, |
956 | | - | }, |
957 | | - | wantProcessed: 5, |
958 | | - | }, |
| 1027 | + | &terminalapi.Keyboard{Key: keyboard.KeyEnter}, |
959 | 1028 | | }, |
960 | | - | |
961 | 1029 | | want: func(size image.Point) *faketerm.Terminal { |
962 | 1030 | | ft := faketerm.MustNew(size) |
963 | 1031 | | |
| skipped 42 lines |
1006 | 1074 | | ), |
1007 | 1075 | | ) |
1008 | 1076 | | }, |
1009 | | - | eventGroups: []*eventGroup{ |
| 1077 | + | events: []terminalapi.Event{ |
1010 | 1078 | | // Move focus to the target container. |
1011 | | - | { |
1012 | | - | events: []terminalapi.Event{ |
1013 | | - | &terminalapi.Mouse{Position: image.Point{39, 19}, Button: mouse.ButtonLeft}, |
1014 | | - | &terminalapi.Mouse{Position: image.Point{39, 19}, Button: mouse.ButtonRelease}, |
1015 | | - | }, |
1016 | | - | wantProcessed: 2, |
1017 | | - | }, |
| 1079 | + | &terminalapi.Mouse{Position: image.Point{39, 19}, Button: mouse.ButtonLeft}, |
| 1080 | + | &terminalapi.Mouse{Position: image.Point{39, 19}, Button: mouse.ButtonRelease}, |
1018 | 1081 | | // Send the keyboard event. |
1019 | | - | { |
1020 | | - | events: []terminalapi.Event{ |
1021 | | - | &terminalapi.Keyboard{Key: keyboard.KeyEnter}, |
1022 | | - | }, |
1023 | | - | wantProcessed: 5, |
1024 | | - | }, |
| 1082 | + | &terminalapi.Keyboard{Key: keyboard.KeyEnter}, |
1025 | 1083 | | }, |
1026 | | - | |
1027 | 1084 | | want: func(size image.Point) *faketerm.Terminal { |
1028 | 1085 | | ft := faketerm.MustNew(size) |
1029 | 1086 | | |
| skipped 32 lines |
1062 | 1119 | | PlaceWidget(fakewidget.New(widgetapi.Options{WantKeyboard: widgetapi.KeyScopeNone})), |
1063 | 1120 | | ) |
1064 | 1121 | | }, |
1065 | | - | eventGroups: []*eventGroup{ |
1066 | | - | { |
1067 | | - | events: []terminalapi.Event{ |
1068 | | - | &terminalapi.Keyboard{Key: keyboard.KeyEnter}, |
1069 | | - | }, |
1070 | | - | wantProcessed: 0, |
1071 | | - | }, |
| 1122 | + | events: []terminalapi.Event{ |
| 1123 | + | &terminalapi.Keyboard{Key: keyboard.KeyEnter}, |
1072 | 1124 | | }, |
1073 | 1125 | | want: func(size image.Point) *faketerm.Terminal { |
1074 | 1126 | | ft := faketerm.MustNew(size) |
| skipped 15 lines |
1090 | 1142 | | PlaceWidget(fakewidget.New(widgetapi.Options{WantKeyboard: widgetapi.KeyScopeFocused})), |
1091 | 1143 | | ) |
1092 | 1144 | | }, |
1093 | | - | eventGroups: []*eventGroup{ |
1094 | | - | { |
1095 | | - | events: []terminalapi.Event{ |
1096 | | - | &terminalapi.Keyboard{Key: keyboard.KeyEsc}, |
1097 | | - | }, |
1098 | | - | wantProcessed: 2, |
1099 | | - | }, |
| 1145 | + | events: []terminalapi.Event{ |
| 1146 | + | &terminalapi.Keyboard{Key: keyboard.KeyEsc}, |
1100 | 1147 | | }, |
| 1148 | + | wantProcessed: 2, // The error is also an event. |
1101 | 1149 | | want: func(size image.Point) *faketerm.Terminal { |
1102 | 1150 | | ft := faketerm.MustNew(size) |
1103 | 1151 | | |
| skipped 28 lines |
1132 | 1180 | | }) |
1133 | 1181 | | |
1134 | 1182 | | c.Subscribe(eds) |
1135 | | - | for _, eg := range tc.eventGroups { |
1136 | | - | for _, ev := range eg.events { |
1137 | | - | eds.Event(ev) |
| 1183 | + | // Initial draw to determine sizes of containers. |
| 1184 | + | if err := c.Draw(); err != nil { |
| 1185 | + | t.Fatalf("Draw => unexpected error: %v", err) |
| 1186 | + | } |
| 1187 | + | for _, ev := range tc.events { |
| 1188 | + | eds.Event(ev) |
| 1189 | + | } |
| 1190 | + | var wantEv int |
| 1191 | + | if tc.wantProcessed != 0 { |
| 1192 | + | wantEv = tc.wantProcessed |
| 1193 | + | } else { |
| 1194 | + | wantEv = len(tc.events) |
| 1195 | + | } |
| 1196 | + | |
| 1197 | + | if err := testevent.WaitFor(5*time.Second, func() error { |
| 1198 | + | if got, want := eds.Processed(), wantEv; got != want { |
| 1199 | + | return fmt.Errorf("the event distribution system processed %d events, want %d", got, want) |
1138 | 1200 | | } |
1139 | | - | if err := testevent.WaitFor(5*time.Second, func() error { |
1140 | | - | if got, want := eds.Processed(), eg.wantProcessed; got != want { |
1141 | | - | return fmt.Errorf("the event distribution system processed %d events, want %d", got, want) |
1142 | | - | } |
1143 | | - | return nil |
1144 | | - | }); err != nil { |
1145 | | - | t.Fatalf("testevent.WaitFor => %v", err) |
1146 | | - | } |
| 1201 | + | return nil |
| 1202 | + | }); err != nil { |
| 1203 | + | t.Fatalf("testevent.WaitFor => %v", err) |
1147 | 1204 | | } |
1148 | 1205 | | |
1149 | 1206 | | if err := c.Draw(); err != nil { |
| skipped 13 lines |
1163 | 1220 | | |
1164 | 1221 | | func TestMouse(t *testing.T) { |
1165 | 1222 | | tests := []struct { |
1166 | | - | desc string |
1167 | | - | termSize image.Point |
1168 | | - | container func(ft *faketerm.Terminal) (*Container, error) |
1169 | | - | events []terminalapi.Event |
1170 | | - | want func(size image.Point) *faketerm.Terminal |
| 1223 | + | desc string |
| 1224 | + | termSize image.Point |
| 1225 | + | container func(ft *faketerm.Terminal) (*Container, error) |
| 1226 | + | events []terminalapi.Event |
| 1227 | + | // If specified, waits for this number of events. |
| 1228 | + | // Otherwise waits for len(events). |
1171 | 1229 | | wantProcessed int |
| 1230 | + | want func(size image.Point) *faketerm.Terminal |
1172 | 1231 | | wantErr bool |
1173 | 1232 | | }{ |
1174 | 1233 | | { |
| skipped 19 lines |
1194 | 1253 | | ) |
1195 | 1254 | | return ft |
1196 | 1255 | | }, |
1197 | | - | wantProcessed: 4, |
1198 | 1256 | | }, |
1199 | 1257 | | { |
1200 | 1258 | | desc: "event not forwarded if container has no widget", |
| skipped 8 lines |
1209 | 1267 | | want: func(size image.Point) *faketerm.Terminal { |
1210 | 1268 | | return faketerm.MustNew(size) |
1211 | 1269 | | }, |
1212 | | - | wantProcessed: 2, |
1213 | 1270 | | }, |
1214 | 1271 | | { |
1215 | 1272 | | desc: "event forwarded to container at that point", |
| skipped 47 lines |
1263 | 1320 | | ) |
1264 | 1321 | | return ft |
1265 | 1322 | | }, |
1266 | | - | wantProcessed: 8, |
1267 | 1323 | | }, |
1268 | 1324 | | { |
1269 | 1325 | | desc: "event focuses the target container after terminal resize (falls onto the new area), regression for #169", |
| skipped 74 lines |
1344 | 1400 | | ) |
1345 | 1401 | | return ft |
1346 | 1402 | | }, |
1347 | | - | wantProcessed: 8, |
1348 | 1403 | | }, |
1349 | 1404 | | { |
1350 | 1405 | | desc: "event not forwarded if the widget didn't request it", |
| skipped 17 lines |
1368 | 1423 | | ) |
1369 | 1424 | | return ft |
1370 | 1425 | | }, |
1371 | | - | wantProcessed: 1, |
1372 | 1426 | | }, |
1373 | 1427 | | { |
1374 | 1428 | | desc: "MouseScopeWidget, event not forwarded if it falls on the container's border", |
| skipped 28 lines |
1403 | 1457 | | ) |
1404 | 1458 | | return ft |
1405 | 1459 | | }, |
1406 | | - | wantProcessed: 2, |
1407 | 1460 | | }, |
1408 | 1461 | | { |
1409 | 1462 | | desc: "MouseScopeContainer, event forwarded if it falls on the container's border", |
| skipped 29 lines |
1439 | 1492 | | ) |
1440 | 1493 | | return ft |
1441 | 1494 | | }, |
1442 | | - | wantProcessed: 2, |
1443 | 1495 | | }, |
1444 | 1496 | | { |
1445 | 1497 | | desc: "MouseScopeGlobal, event forwarded if it falls on the container's border", |
| skipped 29 lines |
1475 | 1527 | | ) |
1476 | 1528 | | return ft |
1477 | 1529 | | }, |
1478 | | - | wantProcessed: 2, |
1479 | 1530 | | }, |
1480 | 1531 | | { |
1481 | 1532 | | desc: "MouseScopeWidget event not forwarded if it falls outside of widget's canvas", |
| skipped 24 lines |
1506 | 1557 | | ) |
1507 | 1558 | | return ft |
1508 | 1559 | | }, |
1509 | | - | wantProcessed: 2, |
1510 | 1560 | | }, |
1511 | 1561 | | { |
1512 | 1562 | | desc: "MouseScopeContainer event forwarded if it falls outside of widget's canvas", |
| skipped 25 lines |
1538 | 1588 | | ) |
1539 | 1589 | | return ft |
1540 | 1590 | | }, |
1541 | | - | wantProcessed: 2, |
1542 | 1591 | | }, |
1543 | 1592 | | { |
1544 | 1593 | | desc: "MouseScopeGlobal event forwarded if it falls outside of widget's canvas", |
| skipped 25 lines |
1570 | 1619 | | ) |
1571 | 1620 | | return ft |
1572 | 1621 | | }, |
1573 | | - | wantProcessed: 2, |
1574 | 1622 | | }, |
1575 | 1623 | | { |
1576 | 1624 | | desc: "MouseScopeWidget event not forwarded if it falls to another container", |
| skipped 29 lines |
1606 | 1654 | | ) |
1607 | 1655 | | return ft |
1608 | 1656 | | }, |
1609 | | - | wantProcessed: 2, |
1610 | 1657 | | }, |
1611 | 1658 | | { |
1612 | 1659 | | desc: "MouseScopeContainer event not forwarded if it falls to another container", |
| skipped 29 lines |
1642 | 1689 | | ) |
1643 | 1690 | | return ft |
1644 | 1691 | | }, |
1645 | | - | wantProcessed: 2, |
1646 | 1692 | | }, |
1647 | 1693 | | { |
1648 | 1694 | | desc: "MouseScopeGlobal event forwarded if it falls to another container", |
| skipped 30 lines |
1679 | 1725 | | ) |
1680 | 1726 | | return ft |
1681 | 1727 | | }, |
1682 | | - | wantProcessed: 2, |
1683 | 1728 | | }, |
1684 | 1729 | | { |
1685 | 1730 | | desc: "mouse position adjusted relative to widget's canvas, vertical offset", |
| skipped 25 lines |
1711 | 1756 | | ) |
1712 | 1757 | | return ft |
1713 | 1758 | | }, |
1714 | | - | wantProcessed: 2, |
1715 | 1759 | | }, |
1716 | 1760 | | { |
1717 | | - | desc: "mouse poisition adjusted relative to widget's canvas, horizontal offset", |
| 1761 | + | desc: "mouse position adjusted relative to widget's canvas, horizontal offset", |
1718 | 1762 | | termSize: image.Point{30, 20}, |
1719 | 1763 | | container: func(ft *faketerm.Terminal) (*Container, error) { |
1720 | 1764 | | return New( |
| skipped 22 lines |
1743 | 1787 | | ) |
1744 | 1788 | | return ft |
1745 | 1789 | | }, |
1746 | | - | wantProcessed: 2, |
1747 | 1790 | | }, |
1748 | 1791 | | { |
1749 | 1792 | | desc: "widget returns an error when processing the event", |
| skipped 7 lines |
1757 | 1800 | | events: []terminalapi.Event{ |
1758 | 1801 | | &terminalapi.Mouse{Position: image.Point{0, 0}, Button: mouse.ButtonRight}, |
1759 | 1802 | | }, |
| 1803 | + | wantProcessed: 2, // The error is also an event. |
1760 | 1804 | | want: func(size image.Point) *faketerm.Terminal { |
1761 | 1805 | | ft := faketerm.MustNew(size) |
1762 | 1806 | | |
| skipped 4 lines |
1767 | 1811 | | ) |
1768 | 1812 | | return ft |
1769 | 1813 | | }, |
1770 | | - | wantProcessed: 3, |
1771 | | - | wantErr: true, |
| 1814 | + | wantErr: true, |
1772 | 1815 | | }, |
1773 | 1816 | | } |
1774 | 1817 | | |
| skipped 16 lines |
1791 | 1834 | | eh.handle(ev.(*terminalapi.Error).Error()) |
1792 | 1835 | | }) |
1793 | 1836 | | c.Subscribe(eds) |
| 1837 | + | // Initial draw to determine sizes of containers. |
| 1838 | + | if err := c.Draw(); err != nil { |
| 1839 | + | t.Fatalf("Draw => unexpected error: %v", err) |
| 1840 | + | } |
1794 | 1841 | | for _, ev := range tc.events { |
1795 | 1842 | | eds.Event(ev) |
1796 | 1843 | | } |
| 1844 | + | var wantEv int |
| 1845 | + | if tc.wantProcessed != 0 { |
| 1846 | + | wantEv = tc.wantProcessed |
| 1847 | + | } else { |
| 1848 | + | wantEv = len(tc.events) |
| 1849 | + | } |
| 1850 | + | |
1797 | 1851 | | if err := testevent.WaitFor(5*time.Second, func() error { |
1798 | | - | if got, want := eds.Processed(), tc.wantProcessed; got != want { |
| 1852 | + | if got, want := eds.Processed(), wantEv; got != want { |
1799 | 1853 | | return fmt.Errorf("the event distribution system processed %d events, want %d", got, want) |
1800 | 1854 | | } |
1801 | 1855 | | return nil |
| skipped 16 lines |
1818 | 1872 | | } |
1819 | 1873 | | } |
1820 | 1874 | | |
| 1875 | + | func TestUpdate(t *testing.T) { |
| 1876 | + | tests := []struct { |
| 1877 | + | desc string |
| 1878 | + | termSize image.Point |
| 1879 | + | container func(ft *faketerm.Terminal) (*Container, error) |
| 1880 | + | updateID string |
| 1881 | + | updateOpts []Option |
| 1882 | + | // events are events delivered before the update. |
| 1883 | + | beforeEvents []terminalapi.Event |
| 1884 | + | // events are events delivered after the update. |
| 1885 | + | afterEvents []terminalapi.Event |
| 1886 | + | wantUpdateErr bool |
| 1887 | + | want func(size image.Point) *faketerm.Terminal |
| 1888 | + | }{ |
| 1889 | + | { |
| 1890 | + | desc: "fails on empty updateID", |
| 1891 | + | termSize: image.Point{10, 10}, |
| 1892 | + | container: func(ft *faketerm.Terminal) (*Container, error) { |
| 1893 | + | return New(ft) |
| 1894 | + | }, |
| 1895 | + | wantUpdateErr: true, |
| 1896 | + | }, |
| 1897 | + | { |
| 1898 | + | desc: "fails when no container with the ID is found", |
| 1899 | + | termSize: image.Point{10, 10}, |
| 1900 | + | container: func(ft *faketerm.Terminal) (*Container, error) { |
| 1901 | + | return New(ft) |
| 1902 | + | }, |
| 1903 | + | updateID: "myID", |
| 1904 | + | wantUpdateErr: true, |
| 1905 | + | }, |
| 1906 | + | { |
| 1907 | + | desc: "no changes when no options are provided", |
| 1908 | + | termSize: image.Point{10, 10}, |
| 1909 | + | container: func(ft *faketerm.Terminal) (*Container, error) { |
| 1910 | + | return New( |
| 1911 | + | ft, |
| 1912 | + | ID("myID"), |
| 1913 | + | Border(linestyle.Light), |
| 1914 | + | ) |
| 1915 | + | }, |
| 1916 | + | updateID: "myID", |
| 1917 | + | want: func(size image.Point) *faketerm.Terminal { |
| 1918 | + | ft := faketerm.MustNew(size) |
| 1919 | + | cvs := testcanvas.MustNew(ft.Area()) |
| 1920 | + | testdraw.MustBorder( |
| 1921 | + | cvs, |
| 1922 | + | image.Rect(0, 0, 10, 10), |
| 1923 | + | draw.BorderCellOpts(cell.FgColor(cell.ColorYellow)), |
| 1924 | + | ) |
| 1925 | + | testcanvas.MustApply(cvs, ft) |
| 1926 | + | return ft |
| 1927 | + | }, |
| 1928 | + | }, |
| 1929 | + | { |
| 1930 | + | desc: "fails on invalid options", |
| 1931 | + | termSize: image.Point{10, 10}, |
| 1932 | + | container: func(ft *faketerm.Terminal) (*Container, error) { |
| 1933 | + | return New( |
| 1934 | + | ft, |
| 1935 | + | ID("myID"), |
| 1936 | + | Border(linestyle.Light), |
| 1937 | + | ) |
| 1938 | + | }, |
| 1939 | + | updateID: "myID", |
| 1940 | + | updateOpts: []Option{ |
| 1941 | + | MarginTop(-1), |
| 1942 | + | }, |
| 1943 | + | wantUpdateErr: true, |
| 1944 | + | }, |
| 1945 | + | { |
| 1946 | + | desc: "fails when update introduces a duplicate ID", |
| 1947 | + | termSize: image.Point{10, 10}, |
| 1948 | + | container: func(ft *faketerm.Terminal) (*Container, error) { |
| 1949 | + | return New( |
| 1950 | + | ft, |
| 1951 | + | ID("myID"), |
| 1952 | + | Border(linestyle.Light), |
| 1953 | + | ) |
| 1954 | + | }, |
| 1955 | + | updateID: "myID", |
| 1956 | + | updateOpts: []Option{ |
| 1957 | + | SplitVertical( |
| 1958 | + | Left( |
| 1959 | + | ID("left"), |
| 1960 | + | ), |
| 1961 | + | Right( |
| 1962 | + | ID("myID"), |
| 1963 | + | ), |
| 1964 | + | ), |
| 1965 | + | }, |
| 1966 | + | wantUpdateErr: true, |
| 1967 | + | }, |
| 1968 | + | { |
| 1969 | + | desc: "removes border from the container", |
| 1970 | + | termSize: image.Point{10, 10}, |
| 1971 | + | container: func(ft *faketerm.Terminal) (*Container, error) { |
| 1972 | + | return New( |
| 1973 | + | ft, |
| 1974 | + | ID("myID"), |
| 1975 | + | Border(linestyle.Light), |
| 1976 | + | ) |
| 1977 | + | }, |
| 1978 | + | updateID: "myID", |
| 1979 | + | updateOpts: []Option{ |
| 1980 | + | Border(linestyle.None), |
| 1981 | + | }, |
| 1982 | + | want: func(size image.Point) *faketerm.Terminal { |
| 1983 | + | return faketerm.MustNew(size) |
| 1984 | + | }, |
| 1985 | + | }, |
| 1986 | + | { |
| 1987 | + | desc: "places widget into a sub-container container", |
| 1988 | + | termSize: image.Point{20, 10}, |
| 1989 | + | container: func(ft *faketerm.Terminal) (*Container, error) { |
| 1990 | + | return New( |
| 1991 | + | ft, |
| 1992 | + | ID("myRoot"), |
| 1993 | + | SplitVertical( |
| 1994 | + | Left( |
| 1995 | + | ID("left"), |
| 1996 | + | ), |
| 1997 | + | Right( |
| 1998 | + | ID("right"), |
| 1999 | + | ), |
| 2000 | + | ), |
| 2001 | + | ) |
| 2002 | + | }, |
| 2003 | + | updateID: "right", |
| 2004 | + | updateOpts: []Option{ |
| 2005 | + | PlaceWidget(fakewidget.New(widgetapi.Options{})), |
| 2006 | + | }, |
| 2007 | + | want: func(size image.Point) *faketerm.Terminal { |
| 2008 | + | ft := faketerm.MustNew(size) |
| 2009 | + | cvs := testcanvas.MustNew(ft.Area()) |
| 2010 | + | wAr := image.Rect(10, 0, 20, 10) |
| 2011 | + | wCvs := testcanvas.MustNew(wAr) |
| 2012 | + | fakewidget.MustDraw(ft, wCvs, widgetapi.Options{}) |
| 2013 | + | testcanvas.MustCopyTo(wCvs, cvs) |
| 2014 | + | testcanvas.MustApply(cvs, ft) |
| 2015 | + | return ft |
| 2016 | + | }, |
| 2017 | + | }, |
| 2018 | + | { |
| 2019 | + | desc: "places widget into root which removes children", |
| 2020 | + | termSize: image.Point{20, 10}, |
| 2021 | + | container: func(ft *faketerm.Terminal) (*Container, error) { |
| 2022 | + | return New( |
| 2023 | + | ft, |
| 2024 | + | ID("myRoot"), |
| 2025 | + | SplitVertical( |
| 2026 | + | Left( |
| 2027 | + | ID("left"), |
| 2028 | + | Border(linestyle.Light), |
| 2029 | + | ), |
| 2030 | + | Right( |
| 2031 | + | ID("right"), |
| 2032 | + | Border(linestyle.Light), |
| 2033 | + | ), |
| 2034 | + | ), |
| 2035 | + | ) |
| 2036 | + | }, |
| 2037 | + | updateID: "myRoot", |
| 2038 | + | updateOpts: []Option{ |
| 2039 | + | PlaceWidget(fakewidget.New(widgetapi.Options{})), |
| 2040 | + | }, |
| 2041 | + | want: func(size image.Point) *faketerm.Terminal { |
| 2042 | + | ft := faketerm.MustNew(size) |
| 2043 | + | cvs := testcanvas.MustNew(ft.Area()) |
| 2044 | + | fakewidget.MustDraw(ft, cvs, widgetapi.Options{}) |
| 2045 | + | testcanvas.MustApply(cvs, ft) |
| 2046 | + | return ft |
| 2047 | + | }, |
| 2048 | + | }, |
| 2049 | + | { |
| 2050 | + | desc: "changes container splits", |
| 2051 | + | termSize: image.Point{10, 10}, |
| 2052 | + | container: func(ft *faketerm.Terminal) (*Container, error) { |
| 2053 | + | return New( |
| 2054 | + | ft, |
| 2055 | + | ID("myRoot"), |
| 2056 | + | SplitVertical( |
| 2057 | + | Left( |
| 2058 | + | ID("left"), |
| 2059 | + | Border(linestyle.Light), |
| 2060 | + | ), |
| 2061 | + | Right( |
| 2062 | + | ID("right"), |
| 2063 | + | Border(linestyle.Light), |
| 2064 | + | ), |
| 2065 | + | ), |
| 2066 | + | ) |
| 2067 | + | }, |
| 2068 | + | updateID: "myRoot", |
| 2069 | + | updateOpts: []Option{ |
| 2070 | + | SplitHorizontal( |
| 2071 | + | Top( |
| 2072 | + | ID("left"), |
| 2073 | + | Border(linestyle.Light), |
| 2074 | + | ), |
| 2075 | + | Bottom( |
| 2076 | + | ID("right"), |
| 2077 | + | Border(linestyle.Light), |
| 2078 | + | ), |
| 2079 | + | ), |
| 2080 | + | }, |
| 2081 | + | want: func(size image.Point) *faketerm.Terminal { |
| 2082 | + | ft := faketerm.MustNew(size) |
| 2083 | + | cvs := testcanvas.MustNew(ft.Area()) |
| 2084 | + | testdraw.MustBorder(cvs, image.Rect(0, 0, 10, 5)) |
| 2085 | + | testdraw.MustBorder(cvs, image.Rect(0, 5, 10, 10)) |
| 2086 | + | testcanvas.MustApply(cvs, ft) |
| 2087 | + | return ft |
| 2088 | + | }, |
| 2089 | + | }, |
| 2090 | + | { |
| 2091 | + | desc: "update retains original focused container if it still exists", |
| 2092 | + | termSize: image.Point{10, 10}, |
| 2093 | + | container: func(ft *faketerm.Terminal) (*Container, error) { |
| 2094 | + | return New( |
| 2095 | + | ft, |
| 2096 | + | ID("myRoot"), |
| 2097 | + | SplitVertical( |
| 2098 | + | Left( |
| 2099 | + | ID("left"), |
| 2100 | + | Border(linestyle.Light), |
| 2101 | + | ), |
| 2102 | + | Right( |
| 2103 | + | ID("right"), |
| 2104 | + | Border(linestyle.Light), |
| 2105 | + | SplitHorizontal( |
| 2106 | + | Top( |
| 2107 | + | ID("rightTop"), |
| 2108 | + | Border(linestyle.Light), |
| 2109 | + | ), |
| 2110 | + | Bottom( |
| 2111 | + | ID("rightBottom"), |
| 2112 | + | Border(linestyle.Light), |
| 2113 | + | ), |
| 2114 | + | ), |
| 2115 | + | ), |
| 2116 | + | ), |
| 2117 | + | ) |
| 2118 | + | }, |
| 2119 | + | beforeEvents: []terminalapi.Event{ |
| 2120 | + | // Move focus to container with ID "right". |
| 2121 | + | // It will continue to exist after the update. |
| 2122 | + | &terminalapi.Mouse{Position: image.Point{5, 0}, Button: mouse.ButtonLeft}, |
| 2123 | + | &terminalapi.Mouse{Position: image.Point{5, 0}, Button: mouse.ButtonRelease}, |
| 2124 | + | }, |
| 2125 | + | updateID: "right", |
| 2126 | + | updateOpts: []Option{ |
| 2127 | + | Clear(), |
| 2128 | + | }, |
| 2129 | + | want: func(size image.Point) *faketerm.Terminal { |
| 2130 | + | ft := faketerm.MustNew(size) |
| 2131 | + | cvs := testcanvas.MustNew(ft.Area()) |
| 2132 | + | testdraw.MustBorder(cvs, image.Rect(0, 0, 5, 10)) |
| 2133 | + | testdraw.MustBorder(cvs, image.Rect(5, 0, 10, 10), draw.BorderCellOpts(cell.FgColor(cell.ColorYellow))) |
| 2134 | + | testcanvas.MustApply(cvs, ft) |
| 2135 | + | return ft |
| 2136 | + | }, |
| 2137 | + | }, |
| 2138 | + | { |
| 2139 | + | desc: "update moves focus to the nearest parent when focused container is destroyed", |
| 2140 | + | termSize: image.Point{10, 10}, |
| 2141 | + | container: func(ft *faketerm.Terminal) (*Container, error) { |
| 2142 | + | return New( |
| 2143 | + | ft, |
| 2144 | + | ID("myRoot"), |
| 2145 | + | SplitVertical( |
| 2146 | + | Left( |
| 2147 | + | ID("left"), |
| 2148 | + | Border(linestyle.Light), |
| 2149 | + | ), |
| 2150 | + | Right( |
| 2151 | + | ID("right"), |
| 2152 | + | Border(linestyle.Light), |
| 2153 | + | SplitHorizontal( |
| 2154 | + | Top( |
| 2155 | + | ID("rightTop"), |
| 2156 | + | Border(linestyle.Light), |
| 2157 | + | ), |
| 2158 | + | Bottom( |
| 2159 | + | ID("rightBottom"), |
| 2160 | + | Border(linestyle.Light), |
| 2161 | + | ), |
| 2162 | + | ), |
| 2163 | + | ), |
| 2164 | + | ), |
| 2165 | + | ) |
| 2166 | + | }, |
| 2167 | + | beforeEvents: []terminalapi.Event{ |
| 2168 | + | // Move focus to container with ID "rightTop". |
| 2169 | + | // It will be destroyed by calling update. |
| 2170 | + | &terminalapi.Mouse{Position: image.Point{6, 1}, Button: mouse.ButtonLeft}, |
| 2171 | + | &terminalapi.Mouse{Position: image.Point{6, 1}, Button: mouse.ButtonRelease}, |
| 2172 | + | }, |
| 2173 | + | updateID: "right", |
| 2174 | + | updateOpts: []Option{ |
| 2175 | + | Clear(), |
| 2176 | + | }, |
| 2177 | + | want: func(size image.Point) *faketerm.Terminal { |
| 2178 | + | ft := faketerm.MustNew(size) |
| 2179 | + | cvs := testcanvas.MustNew(ft.Area()) |
| 2180 | + | testdraw.MustBorder(cvs, image.Rect(0, 0, 5, 10)) |
| 2181 | + | testdraw.MustBorder(cvs, image.Rect(5, 0, 10, 10), draw.BorderCellOpts(cell.FgColor(cell.ColorYellow))) |
| 2182 | + | testcanvas.MustApply(cvs, ft) |
| 2183 | + | return ft |
| 2184 | + | }, |
| 2185 | + | }, |
| 2186 | + | { |
| 2187 | + | desc: "newly placed widget gets keyboard events", |
| 2188 | + | termSize: image.Point{10, 10}, |
| 2189 | + | container: func(ft *faketerm.Terminal) (*Container, error) { |
| 2190 | + | return New( |
| 2191 | + | ft, |
| 2192 | + | ID("myRoot"), |
| 2193 | + | PlaceWidget(fakewidget.New(widgetapi.Options{WantKeyboard: widgetapi.KeyScopeFocused})), |
| 2194 | + | ) |
| 2195 | + | }, |
| 2196 | + | beforeEvents: []terminalapi.Event{ |
| 2197 | + | // Move focus to the target container. |
| 2198 | + | &terminalapi.Mouse{Position: image.Point{0, 0}, Button: mouse.ButtonLeft}, |
| 2199 | + | &terminalapi.Mouse{Position: image.Point{0, 0}, Button: mouse.ButtonRelease}, |
| 2200 | + | }, |
| 2201 | + | afterEvents: []terminalapi.Event{ |
| 2202 | + | // Send the keyboard event. |
| 2203 | + | &terminalapi.Keyboard{Key: keyboard.KeyEnter}, |
| 2204 | + | }, |
| 2205 | + | updateID: "myRoot", |
| 2206 | + | updateOpts: []Option{ |
| 2207 | + | PlaceWidget(fakewidget.New(widgetapi.Options{WantKeyboard: widgetapi.KeyScopeFocused})), |
| 2208 | + | }, |
| 2209 | + | want: func(size image.Point) *faketerm.Terminal { |
| 2210 | + | ft := faketerm.MustNew(size) |
| 2211 | + | cvs := testcanvas.MustNew(ft.Area()) |
| 2212 | + | fakewidget.MustDraw( |
| 2213 | + | ft, |
| 2214 | + | cvs, |
| 2215 | + | widgetapi.Options{WantKeyboard: widgetapi.KeyScopeFocused}, |
| 2216 | + | &terminalapi.Keyboard{Key: keyboard.KeyEnter}, |
| 2217 | + | ) |
| 2218 | + | testcanvas.MustApply(cvs, ft) |
| 2219 | + | return ft |
| 2220 | + | }, |
| 2221 | + | }, |
| 2222 | + | { |
| 2223 | + | desc: "newly placed widget gets mouse events", |
| 2224 | + | termSize: image.Point{20, 10}, |
| 2225 | + | container: func(ft *faketerm.Terminal) (*Container, error) { |
| 2226 | + | return New( |
| 2227 | + | ft, |
| 2228 | + | ID("myRoot"), |
| 2229 | + | PlaceWidget(fakewidget.New(widgetapi.Options{WantMouse: widgetapi.MouseScopeWidget})), |
| 2230 | + | ) |
| 2231 | + | }, |
| 2232 | + | afterEvents: []terminalapi.Event{ |
| 2233 | + | &terminalapi.Mouse{Position: image.Point{0, 0}, Button: mouse.ButtonLeft}, |
| 2234 | + | &terminalapi.Mouse{Position: image.Point{0, 0}, Button: mouse.ButtonRelease}, |
| 2235 | + | }, |
| 2236 | + | updateID: "myRoot", |
| 2237 | + | updateOpts: []Option{ |
| 2238 | + | PlaceWidget(fakewidget.New(widgetapi.Options{WantMouse: widgetapi.MouseScopeWidget})), |
| 2239 | + | }, |
| 2240 | + | want: func(size image.Point) *faketerm.Terminal { |
| 2241 | + | ft := faketerm.MustNew(size) |
| 2242 | + | cvs := testcanvas.MustNew(ft.Area()) |
| 2243 | + | fakewidget.MustDraw( |
| 2244 | + | ft, |
| 2245 | + | cvs, |
| 2246 | + | widgetapi.Options{WantMouse: widgetapi.MouseScopeWidget}, |
| 2247 | + | &terminalapi.Mouse{Position: image.Point{0, 0}, Button: mouse.ButtonRelease}, |
| 2248 | + | ) |
| 2249 | + | testcanvas.MustApply(cvs, ft) |
| 2250 | + | return ft |
| 2251 | + | }, |
| 2252 | + | }, |
| 2253 | + | } |
| 2254 | + | |
| 2255 | + | for _, tc := range tests { |
| 2256 | + | t.Run(tc.desc, func(t *testing.T) { |
| 2257 | + | got, err := faketerm.New(tc.termSize) |
| 2258 | + | if err != nil { |
| 2259 | + | t.Fatalf("faketerm.New => unexpected error: %v", err) |
| 2260 | + | } |
| 2261 | + | |
| 2262 | + | cont, err := tc.container(got) |
| 2263 | + | if err != nil { |
| 2264 | + | t.Fatalf("tc.container => unexpected error: %v", err) |
| 2265 | + | } |
| 2266 | + | |
| 2267 | + | eds := event.NewDistributionSystem() |
| 2268 | + | eh := &errorHandler{} |
| 2269 | + | // Subscribe to receive errors. |
| 2270 | + | eds.Subscribe([]terminalapi.Event{terminalapi.NewError("")}, func(ev terminalapi.Event) { |
| 2271 | + | eh.handle(ev.(*terminalapi.Error).Error()) |
| 2272 | + | }) |
| 2273 | + | cont.Subscribe(eds) |
| 2274 | + | // Initial draw to determine sizes of containers. |
| 2275 | + | if err := cont.Draw(); err != nil { |
| 2276 | + | t.Fatalf("Draw => unexpected error: %v", err) |
| 2277 | + | } |
| 2278 | + | |
| 2279 | + | // Deliver the before events. |
| 2280 | + | for _, ev := range tc.beforeEvents { |
| 2281 | + | eds.Event(ev) |
| 2282 | + | } |
| 2283 | + | if err := testevent.WaitFor(5*time.Second, func() error { |
| 2284 | + | if got, want := eds.Processed(), len(tc.beforeEvents); got != want { |
| 2285 | + | return fmt.Errorf("the event distribution system processed %d events, want %d", got, want) |
| 2286 | + | } |
| 2287 | + | return nil |
| 2288 | + | }); err != nil { |
| 2289 | + | t.Fatalf("testevent.WaitFor => %v", err) |
| 2290 | + | } |
| 2291 | + | |
| 2292 | + | { |
| 2293 | + | err := cont.Update(tc.updateID, tc.updateOpts...) |
| 2294 | + | if (err != nil) != tc.wantUpdateErr { |
| 2295 | + | t.Errorf("Update => unexpected error:%v, wantErr:%v", err, tc.wantUpdateErr) |
| 2296 | + | } |
| 2297 | + | if err != nil { |
| 2298 | + | return |
| 2299 | + | } |
| 2300 | + | } |
| 2301 | + | |
| 2302 | + | // Deliver the after events. |
| 2303 | + | for _, ev := range tc.afterEvents { |
| 2304 | + | eds.Event(ev) |
| 2305 | + | } |
| 2306 | + | wantEv := len(tc.beforeEvents) + len(tc.afterEvents) |
| 2307 | + | if err := testevent.WaitFor(5*time.Second, func() error { |
| 2308 | + | if got, want := eds.Processed(), wantEv; got != want { |
| 2309 | + | return fmt.Errorf("the event distribution system processed %d events, want %d", got, want) |
| 2310 | + | } |
| 2311 | + | return nil |
| 2312 | + | }); err != nil { |
| 2313 | + | t.Fatalf("testevent.WaitFor => %v", err) |
| 2314 | + | } |
| 2315 | + | |
| 2316 | + | if err := cont.Draw(); err != nil { |
| 2317 | + | t.Fatalf("Draw => unexpected error: %v", err) |
| 2318 | + | } |
| 2319 | + | |
| 2320 | + | var want *faketerm.Terminal |
| 2321 | + | if tc.want != nil { |
| 2322 | + | want = tc.want(tc.termSize) |
| 2323 | + | } else { |
| 2324 | + | w, err := faketerm.New(tc.termSize) |
| 2325 | + | if err != nil { |
| 2326 | + | t.Fatalf("faketerm.New => unexpected error: %v", err) |
| 2327 | + | } |
| 2328 | + | want = w |
| 2329 | + | } |
| 2330 | + | if diff := faketerm.Diff(want, got); diff != "" { |
| 2331 | + | t.Errorf("Draw => %v", diff) |
| 2332 | + | } |
| 2333 | + | }) |
| 2334 | + | } |
| 2335 | + | |
| 2336 | + | } |
| 2337 | + | |