|
18 | 18 | * |
19 | 19 | */ |
20 | 20 |
|
21 | | -/*global describe, it, expect, beforeAll, afterAll, beforeEach, afterEach, awaitsFor */ |
| 21 | +/*global describe, it, expect, beforeAll, afterAll, beforeEach, afterEach, awaitsFor, awaits */ |
22 | 22 |
|
23 | 23 | define(function (require, exports, module) { |
24 | 24 |
|
@@ -115,6 +115,16 @@ define(function (require, exports, module) { |
115 | 115 | CommandManager.execute(Commands.VIEW_TOGGLE_DESIGN_MODE); |
116 | 116 | await awaitsFor(function () { return WorkspaceManager.isInDesignMode(); }, |
117 | 117 | "design mode to activate", 10000); |
| 118 | + // isInDesignMode() flips as soon as the body class is added, but when LP |
| 119 | + // wasn't already open the toggle re-enters through a pending LP-open |
| 120 | + // promise and only runs _applyCollapsedLayout (which sets sidebar |
| 121 | + // data-maxsize to "1000%") after that resolves. Wait for LP to be visible |
| 122 | + // AND the collapsed layout to have been applied so subsequent drag tests |
| 123 | + // see the fully-settled design-mode geometry. |
| 124 | + await awaitsFor(function () { |
| 125 | + const p = livePanel(); |
| 126 | + return p && p.isVisible() && _$("#sidebar").data("maxsize") === "1000%"; |
| 127 | + }, "design-mode layout to be applied", 10000); |
118 | 128 | } |
119 | 129 |
|
120 | 130 | async function exitDesignMode() { |
@@ -440,7 +450,7 @@ define(function (require, exports, module) { |
440 | 450 | for (let i = 0; i < 10; i++) { |
441 | 451 | testWindow.dispatchEvent(new testWindow.Event("resize")); |
442 | 452 | } |
443 | | - await awaitsFor(function () { return true; }, "a tick", 100); |
| 453 | + await awaits(0); |
444 | 454 |
|
445 | 455 | // The sidebar is pinned — Resizer.updateResizeLimits shouldn't shrink it. |
446 | 456 | expect(_$("#sidebar")[0].offsetWidth).toBe(startWidth); |
@@ -571,5 +581,252 @@ define(function (require, exports, module) { |
571 | 581 | expect(Math.abs(toolbarW - iconsW)).toBeLessThan(3); |
572 | 582 | }); |
573 | 583 | }); |
| 584 | + |
| 585 | + describe("7. Sidebar drag in design mode", function () { |
| 586 | + // Capture all console.error messages emitted during each test so the |
| 587 | + // ResizeObserver-warning test can assert on them. Installed/removed via |
| 588 | + // beforeEach/afterEach so Jasmine itself guarantees the restore — the |
| 589 | + // spy can't leak into later tests even if a drag helper throws or an |
| 590 | + // expect() fails mid-test. |
| 591 | + let consoleErrors; |
| 592 | + let origConsoleError; |
| 593 | + |
| 594 | + beforeEach(async function () { |
| 595 | + SidebarView.resize(200); |
| 596 | + await awaitsFor(function () { return _$("#sidebar")[0].offsetWidth === 200; }, |
| 597 | + "sidebar to settle at baseline 200px", 2000); |
| 598 | + await enterDesignMode(); |
| 599 | + |
| 600 | + consoleErrors = []; |
| 601 | + origConsoleError = testWindow.console.error; |
| 602 | + testWindow.console.error = function () { |
| 603 | + consoleErrors.push(Array.prototype.slice.call(arguments).map(String).join(" ")); |
| 604 | + return origConsoleError.apply(testWindow.console, arguments); |
| 605 | + }; |
| 606 | + }); |
| 607 | + |
| 608 | + afterEach(function () { |
| 609 | + testWindow.console.error = origConsoleError; |
| 610 | + consoleErrors = null; |
| 611 | + origConsoleError = null; |
| 612 | + }); |
| 613 | + |
| 614 | + it("should grow the sidebar roughly 1:1 with the drag delta up to the CSS cap", async function () { |
| 615 | + const beforeWidth = _$("#sidebar")[0].offsetWidth; |
| 616 | + const $resizer = _$("#sidebar > .horz-resizer"); |
| 617 | + const rect = $resizer[0].getBoundingClientRect(); |
| 618 | + const handleY = rect.top + rect.height / 2; |
| 619 | + const dragDelta = 150; |
| 620 | + |
| 621 | + await DragTestUtils.dragFromElement($resizer[0], |
| 622 | + rect.left + rect.width / 2 + dragDelta, handleY, testWindow); |
| 623 | + |
| 624 | + const afterWidth = _$("#sidebar")[0].offsetWidth; |
| 625 | + // 1:1 tracking up to the cap — tolerate a handful of pixels for |
| 626 | + // sub-pixel accumulation across steps. |
| 627 | + expect(Math.abs((afterWidth - beforeWidth) - dragDelta)).toBeLessThan(10); |
| 628 | + }); |
| 629 | + |
| 630 | + it("should cap the rendered sidebar at calc(100vw - 230px) even when dragged far past it", async function () { |
| 631 | + const $resizer = _$("#sidebar > .horz-resizer"); |
| 632 | + const rect = $resizer[0].getBoundingClientRect(); |
| 633 | + const handleY = rect.top + rect.height / 2; |
| 634 | + const cap = testWindow.innerWidth - 230; |
| 635 | + |
| 636 | + // Drag well past the cap — final mouse position near the right edge. |
| 637 | + await DragTestUtils.dragFromElement($resizer[0], |
| 638 | + testWindow.innerWidth - 10, handleY, testWindow); |
| 639 | + |
| 640 | + const rendered = _$("#sidebar")[0].offsetWidth; |
| 641 | + // Rendered width hits the cap but never crosses it. |
| 642 | + expect(rendered).toBeLessThanOrEqual(cap + 1); |
| 643 | + expect(rendered).toBeGreaterThan(cap - 20); |
| 644 | + }); |
| 645 | + |
| 646 | + it("should not emit ResizeObserver loop warnings during a capped drag", async function () { |
| 647 | + const $resizer = _$("#sidebar > .horz-resizer"); |
| 648 | + const rect = $resizer[0].getBoundingClientRect(); |
| 649 | + const handleY = rect.top + rect.height / 2; |
| 650 | + await DragTestUtils.dragFromElement($resizer[0], |
| 651 | + testWindow.innerWidth - 10, handleY, testWindow, 16); |
| 652 | + |
| 653 | + const resizeObserverWarnings = consoleErrors.filter(function (msg) { |
| 654 | + return /ResizeObserver/i.test(msg); |
| 655 | + }); |
| 656 | + expect(resizeObserverWarnings).toEqual([]); |
| 657 | + }); |
| 658 | + |
| 659 | + it("should let the user collapse the sidebar via drag in design mode (CCB toggle remains to re-open)", async function () { |
| 660 | + // Drag all the way left past the sidebar's own left edge. |
| 661 | + const $resizer = _$("#sidebar > .horz-resizer"); |
| 662 | + const rect = $resizer[0].getBoundingClientRect(); |
| 663 | + const handleY = rect.top + rect.height / 2; |
| 664 | + const sidebarLeft = _$("#sidebar")[0].getBoundingClientRect().left; |
| 665 | + |
| 666 | + await DragTestUtils.dragFromElement($resizer[0], sidebarLeft - 100, handleY, testWindow); |
| 667 | + |
| 668 | + // Design mode honours the same collapse-via-drag affordance as normal |
| 669 | + // mode (see "1. Layout"). The CCB sidebar-toggle stays put so the user |
| 670 | + // can bring the sidebar back. |
| 671 | + expect(SidebarView.isVisible()).toBe(false); |
| 672 | + expect(_$("#ccbSidebarToggleBtn").is(":visible")).toBe(true); |
| 673 | + }); |
| 674 | + |
| 675 | + it("should forward panelResizeStart / panelResizeUpdate / panelResizeEnd from the sidebar drag to #main-toolbar", async function () { |
| 676 | + const events = []; |
| 677 | + const $mt = _$("#main-toolbar"); |
| 678 | + const record = function (e) { events.push(e.type); }; |
| 679 | + $mt.on("panelResizeStart.test panelResizeUpdate.test panelResizeEnd.test", record); |
| 680 | + |
| 681 | + try { |
| 682 | + const $resizer = _$("#sidebar > .horz-resizer"); |
| 683 | + const rect = $resizer[0].getBoundingClientRect(); |
| 684 | + const handleY = rect.top + rect.height / 2; |
| 685 | + await DragTestUtils.dragFromElement($resizer[0], |
| 686 | + rect.left + 100, handleY, testWindow); |
| 687 | + // Wait for the forwarded end event — it travels through CCB's |
| 688 | + // `panelResizeEnd` handler which may still be in-flight when the |
| 689 | + // drag helper returns. |
| 690 | + await awaitsFor(function () { return events.indexOf("panelResizeEnd") !== -1; }, |
| 691 | + "panelResizeEnd to be forwarded to #main-toolbar", 2000); |
| 692 | + } finally { |
| 693 | + $mt.off(".test"); |
| 694 | + } |
| 695 | + |
| 696 | + // All three lifecycle events must fire on #main-toolbar so downstream |
| 697 | + // listeners (lpedit-helper media-query ruler) can track the drag. |
| 698 | + expect(events).toContain("panelResizeStart"); |
| 699 | + expect(events).toContain("panelResizeUpdate"); |
| 700 | + expect(events).toContain("panelResizeEnd"); |
| 701 | + }); |
| 702 | + }); |
| 703 | + |
| 704 | + describe("8. Window resize while in design mode", function () { |
| 705 | + |
| 706 | + beforeEach(async function () { |
| 707 | + SidebarView.resize(200); |
| 708 | + await awaitsFor(function () { return _$("#sidebar")[0].offsetWidth === 200; }, |
| 709 | + "sidebar to settle at baseline 200px", 2000); |
| 710 | + await enterDesignMode(); |
| 711 | + }); |
| 712 | + |
| 713 | + it("should keep sidebar width stable across a burst of 20 synthetic window resizes", async function () { |
| 714 | + const startWidth = _$("#sidebar")[0].offsetWidth; |
| 715 | + for (let i = 0; i < 20; i++) { |
| 716 | + testWindow.dispatchEvent(new testWindow.Event("resize")); |
| 717 | + } |
| 718 | + // Let any deferred handlers flush. |
| 719 | + await awaits(0); |
| 720 | + expect(_$("#sidebar")[0].offsetWidth).toBe(startWidth); |
| 721 | + }); |
| 722 | + |
| 723 | + it("should keep #main-toolbar flush with the right edge of the window after resize bursts", async function () { |
| 724 | + for (let i = 0; i < 10; i++) { |
| 725 | + testWindow.dispatchEvent(new testWindow.Event("resize")); |
| 726 | + } |
| 727 | + await awaits(0); |
| 728 | + |
| 729 | + const mtRect = _$("#main-toolbar")[0].getBoundingClientRect(); |
| 730 | + expect(Math.abs(mtRect.right - testWindow.innerWidth)).toBeLessThan(2); |
| 731 | + }); |
| 732 | + |
| 733 | + it("should give #main-toolbar the full (window - CCB) width when sidebar is hidden", async function () { |
| 734 | + SidebarView.hide(); |
| 735 | + await awaitsFor(function () { return !SidebarView.isVisible(); }, |
| 736 | + "sidebar to hide", 2000); |
| 737 | + |
| 738 | + // Force a relayout pass by firing a resize so the collapsed-layout |
| 739 | + // reassertion runs against the new sidebar-hidden geometry. |
| 740 | + testWindow.dispatchEvent(new testWindow.Event("resize")); |
| 741 | + await awaits(0); |
| 742 | + |
| 743 | + const mtW = _$("#main-toolbar").outerWidth(); |
| 744 | + const expected = testWindow.innerWidth - CCB_WIDTH; |
| 745 | + // No ~70–300px phantom gap from earlier WSM clamping bugs. |
| 746 | + expect(Math.abs(mtW - expected)).toBeLessThan(5); |
| 747 | + }); |
| 748 | + }); |
| 749 | + |
| 750 | + describe("9. Plugin toolbar resizer", function () { |
| 751 | + |
| 752 | + it("should let the user drag the main-toolbar's left-edge handle to resize the panel in normal mode", async function () { |
| 753 | + await openLivePreview(); |
| 754 | + // Reset to a predictable modest width before the drag so the assertion |
| 755 | + // isn't sitting at the 75% clamp from whatever previous test left. |
| 756 | + const iconsW = _$("#plugin-icons-bar").outerWidth(); |
| 757 | + const startTarget = 300; |
| 758 | + WorkspaceManager.setPluginPanelWidth(startTarget - iconsW); |
| 759 | + await awaitsFor(function () { |
| 760 | + return Math.abs(_$("#main-toolbar").outerWidth() - startTarget) < 3; |
| 761 | + }, "main-toolbar to settle at 300px", 3000); |
| 762 | + |
| 763 | + const beforeWidth = _$("#main-toolbar").outerWidth(); |
| 764 | + const resizer = _$("#main-toolbar > .horz-resizer")[0]; |
| 765 | + const rect = resizer.getBoundingClientRect(); |
| 766 | + const handleY = rect.top + rect.height / 2; |
| 767 | + const delta = 120; |
| 768 | + |
| 769 | + // Drag leftward to widen the toolbar by ~delta. |
| 770 | + await DragTestUtils.dragFromElement(resizer, |
| 771 | + rect.left + rect.width / 2 - delta, handleY, testWindow); |
| 772 | + |
| 773 | + const afterWidth = _$("#main-toolbar").outerWidth(); |
| 774 | + expect(afterWidth).toBeGreaterThan(beforeWidth); |
| 775 | + expect(Math.abs((afterWidth - beforeWidth) - delta)).toBeLessThan(20); |
| 776 | + }); |
| 777 | + }); |
| 778 | + |
| 779 | + describe("10. WorkspaceManager.setPluginPanelWidth", function () { |
| 780 | + |
| 781 | + beforeEach(async function () { |
| 782 | + SidebarView.resize(200); |
| 783 | + await awaitsFor(function () { return _$("#sidebar")[0].offsetWidth === 200; }, |
| 784 | + "sidebar to settle at baseline 200px", 2000); |
| 785 | + }); |
| 786 | + |
| 787 | + it("should, in design mode, translate a requested plugin-panel width into a sidebar width so the layout fits", async function () { |
| 788 | + await enterDesignMode(); |
| 789 | + |
| 790 | + const iconsW = _$("#plugin-icons-bar").outerWidth(); |
| 791 | + const requested = 400; |
| 792 | + WorkspaceManager.setPluginPanelWidth(requested); |
| 793 | + await awaits(0); |
| 794 | + |
| 795 | + // Expected sidebar = window - (requested + iconsBar) - CCB, clamped at 0. |
| 796 | + const expectedSidebar = Math.max(0, |
| 797 | + testWindow.innerWidth - (requested + iconsW) - CCB_WIDTH); |
| 798 | + expect(Math.abs(_$("#sidebar")[0].offsetWidth - expectedSidebar)).toBeLessThan(3); |
| 799 | + |
| 800 | + // And the main-toolbar takes the remaining right-hand room. |
| 801 | + const mtRect = _$("#main-toolbar")[0].getBoundingClientRect(); |
| 802 | + expect(Math.abs(mtRect.right - testWindow.innerWidth)).toBeLessThan(2); |
| 803 | + }); |
| 804 | + |
| 805 | + it("should, in normal mode, resize only the plugin-panel (sidebar untouched) and respect the 75%/sidebar clamp", async function () { |
| 806 | + await openLivePreview(); |
| 807 | + const iconsW = _$("#plugin-icons-bar").outerWidth(); |
| 808 | + const sidebarBefore = _$("#sidebar")[0].offsetWidth; |
| 809 | + |
| 810 | + // Request a very wide panel — should be clamped against 75% window |
| 811 | + // and (window - sidebar - 100). Either way sidebar must stay put. |
| 812 | + const requested = testWindow.innerWidth; // intentionally over-large |
| 813 | + WorkspaceManager.setPluginPanelWidth(requested); |
| 814 | + await awaits(0); |
| 815 | + |
| 816 | + expect(_$("#sidebar")[0].offsetWidth).toBe(sidebarBefore); |
| 817 | + |
| 818 | + const toolbar = _$("#main-toolbar").outerWidth(); |
| 819 | + const maxAllowed = Math.min( |
| 820 | + testWindow.innerWidth * 0.75, |
| 821 | + testWindow.innerWidth - sidebarBefore - 100 |
| 822 | + ); |
| 823 | + // Toolbar must honour the clamp (+iconsBar = the WSM math). |
| 824 | + expect(toolbar).toBeLessThanOrEqual(maxAllowed + 3); |
| 825 | + // And at minimum it's the icons-bar + LP's minWidth. |
| 826 | + const lp = livePanel(); |
| 827 | + const minToolbar = (lp && lp.minWidth ? lp.minWidth : 0) + iconsW; |
| 828 | + expect(toolbar).toBeGreaterThanOrEqual(minToolbar); |
| 829 | + }); |
| 830 | + }); |
574 | 831 | }); |
575 | 832 | }); |
0 commit comments