UNPKG

ipsos-components

Version:

Material Design components for Angular

786 lines (635 loc) 30.9 kB
import {ElementRef} from '@angular/core'; import {TestBed, inject} from '@angular/core/testing'; import {OverlayPositionBuilder} from './overlay-position-builder'; import {CdkScrollable} from '@angular/cdk/scrolling'; import {Subscription} from 'rxjs/Subscription'; import {ScrollDispatchModule} from '@angular/cdk/scrolling'; import { OverlayModule, Overlay, OverlayRef, OverlayContainer, ConnectedPositionStrategy, ConnectedOverlayPositionChange, } from '../index'; // Default width and height of the overlay and origin panels throughout these tests. const DEFAULT_HEIGHT = 30; const DEFAULT_WIDTH = 60; // For all tests, we assume the browser window is 1024x786 (outerWidth x outerHeight). // The karma config has been set to this for local tests, and it is the default size // for tests on CI (both SauceLabs and Browserstack). describe('ConnectedPositionStrategy', () => { let positionBuilder: OverlayPositionBuilder; let overlayContainer: OverlayContainer; let overlayContainerElement: HTMLElement; beforeEach(() => { TestBed.configureTestingModule({imports: [ScrollDispatchModule, OverlayModule]}); inject([Overlay, OverlayContainer], (overlay: Overlay, oc: OverlayContainer) => { positionBuilder = overlay.position(); overlayContainer = oc; overlayContainerElement = oc.getContainerElement(); })(); }); afterEach(() => { overlayContainer.ngOnDestroy(); }); describe('with origin on document body', () => { const ORIGIN_HEIGHT = DEFAULT_HEIGHT; const ORIGIN_WIDTH = DEFAULT_WIDTH; const OVERLAY_HEIGHT = DEFAULT_HEIGHT; const OVERLAY_WIDTH = DEFAULT_WIDTH; let originElement: HTMLElement; let overlayElement: HTMLElement; let strategy: ConnectedPositionStrategy; let fakeElementRef: ElementRef; let originRect: ClientRect | null; let originCenterX: number | null; let originCenterY: number | null; beforeEach(() => { // The origin and overlay elements need to be in the document body in order to have geometry. originElement = createPositionedBlockElement(); overlayElement = createPositionedBlockElement(); document.body.appendChild(originElement); overlayContainerElement.appendChild(overlayElement); fakeElementRef = new ElementRef(originElement); }); afterEach(() => { document.body.removeChild(originElement); // Reset the origin geometry after each test so we don't accidently keep state between tests. originRect = null; originCenterX = null; originCenterY = null; }); describe('when not near viewport edge, not scrolled', () => { // Place the original element close to the center of the window. // (1024 / 2, 768 / 2). It's not exact, since outerWidth/Height includes browser // chrome, but it doesn't really matter for these tests. const ORIGIN_LEFT = 500; const ORIGIN_TOP = 350; beforeEach(() => { originElement.style.left = `${ORIGIN_LEFT}px`; originElement.style.top = `${ORIGIN_TOP}px`; originRect = originElement.getBoundingClientRect(); originCenterX = originRect.left + (ORIGIN_WIDTH / 2); originCenterY = originRect.top + (ORIGIN_HEIGHT / 2); }); // Preconditions are set, now just run the full set of simple position tests. runSimplePositionTests(); }); describe('when scrolled', () => { // Place the original element decently far outside the unscrolled document (1024x768). const ORIGIN_LEFT = 2500; const ORIGIN_TOP = 2500; // Create a very large element that will make the page scrollable. let veryLargeElement: HTMLElement = document.createElement('div'); veryLargeElement.style.width = '4000px'; veryLargeElement.style.height = '4000px'; beforeEach(() => { // Scroll the page such that the origin element is roughly in the // center of the visible viewport (2500 - 1024/2, 2500 - 768/2). document.body.appendChild(veryLargeElement); document.body.scrollTop = 2100; document.body.scrollLeft = 2100; originElement.style.top = `${ORIGIN_TOP}px`; originElement.style.left = `${ORIGIN_LEFT}px`; originRect = originElement.getBoundingClientRect(); originCenterX = originRect.left + (ORIGIN_WIDTH / 2); originCenterY = originRect.top + (ORIGIN_HEIGHT / 2); }); afterEach(() => { document.body.removeChild(veryLargeElement); document.body.scrollTop = 0; document.body.scrollLeft = 0; }); // Preconditions are set, now just run the full set of simple position tests. runSimplePositionTests(); }); describe('when near viewport edge', () => { it('should reposition the overlay if it would go off the top of the screen', () => { // We can use the real ViewportRuler in this test since we know that zero is // always the top of the viewport. originElement.style.top = '5px'; originElement.style.left = '200px'; originRect = originElement.getBoundingClientRect(); strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'end', originY: 'top'}, {overlayX: 'end', overlayY: 'bottom'}) .withFallbackPosition( {originX: 'start', originY: 'bottom'}, {overlayX: 'start', overlayY: 'top'}); strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); let overlayRect = overlayElement.getBoundingClientRect(); expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect.bottom)); expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect.left)); }); it('should reposition the overlay if it would go off the left of the screen', () => { // We can use the real ViewportRuler in this test since we know that zero is // always the left edge of the viewport. originElement.style.top = '200px'; originElement.style.left = '5px'; originRect = originElement.getBoundingClientRect(); originCenterY = originRect.top + (ORIGIN_HEIGHT / 2); strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'start', originY: 'bottom'}, {overlayX: 'end', overlayY: 'top'}) .withFallbackPosition( {originX: 'end', originY: 'center'}, {overlayX: 'start', overlayY: 'center'}); strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); let overlayRect = overlayElement.getBoundingClientRect(); expect(Math.floor(overlayRect.top)).toBe(Math.floor(originCenterY - (OVERLAY_HEIGHT / 2))); expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect.right)); }); it('should reposition the overlay if it would go off the bottom of the screen', () => { originElement.style.bottom = '25px'; originElement.style.left = '200px'; originRect = originElement.getBoundingClientRect(); strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'start', originY: 'bottom'}, {overlayX: 'start', overlayY: 'top'}) .withFallbackPosition( {originX: 'end', originY: 'top'}, {overlayX: 'end', overlayY: 'bottom'}); strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); let overlayRect = overlayElement.getBoundingClientRect(); expect(Math.floor(overlayRect.bottom)).toBe(Math.floor(originRect.top)); expect(Math.floor(overlayRect.right)).toBe(Math.floor(originRect.right)); }); it('should reposition the overlay if it would go off the right of the screen', () => { originElement.style.top = '200px'; originElement.style.right = '25px'; originRect = originElement.getBoundingClientRect(); strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'end', originY: 'center'}, {overlayX: 'start', overlayY: 'center'}) .withFallbackPosition( {originX: 'start', originY: 'bottom'}, {overlayX: 'end', overlayY: 'top'}); strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); let overlayRect = overlayElement.getBoundingClientRect(); expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect.bottom)); expect(Math.floor(overlayRect.right)).toBe(Math.floor(originRect.left)); }); it('should recalculate and set the last position with recalculateLastPosition()', () => { // Push the trigger down so the overlay doesn't have room to open on the bottom. originElement.style.bottom = '25px'; originRect = originElement.getBoundingClientRect(); strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'start', originY: 'bottom'}, {overlayX: 'start', overlayY: 'top'}) .withFallbackPosition( {originX: 'start', originY: 'top'}, {overlayX: 'start', overlayY: 'bottom'}); // This should apply the fallback position, as the original position won't fit. strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); // Now make the overlay small enough to fit in the first preferred position. overlayElement.style.height = '15px'; // This should only re-align in the last position, even though the first would fit. strategy.recalculateLastPosition(); let overlayRect = overlayElement.getBoundingClientRect(); expect(Math.floor(overlayRect.bottom)).toBe(Math.floor(originRect.top), 'Expected overlay to be re-aligned to the trigger in the previous position.'); }); it('should default to the initial position, if no positions fit in the viewport', () => { // Make the origin element taller than the viewport. originElement.style.height = '1000px'; originElement.style.top = '0'; originRect = originElement.getBoundingClientRect(); strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'start', originY: 'top'}, {overlayX: 'start', overlayY: 'bottom'}); strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); strategy.recalculateLastPosition(); let overlayRect = overlayElement.getBoundingClientRect(); expect(Math.floor(overlayRect.bottom)).toBe(Math.floor(originRect.top), 'Expected overlay to be re-aligned to the trigger in the initial position.'); }); it('should position a panel properly when rtl', () => { // must make the overlay longer than the origin to properly test attachment overlayElement.style.width = `500px`; originRect = originElement.getBoundingClientRect(); strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'start', originY: 'bottom'}, {overlayX: 'start', overlayY: 'top'}) .withDirection('rtl'); strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); let overlayRect = overlayElement.getBoundingClientRect(); expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect.bottom)); expect(Math.floor(overlayRect.right)).toBe(Math.floor(originRect.right)); }); it('should position a panel with the x offset provided', () => { originRect = originElement.getBoundingClientRect(); strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'start', originY: 'top'}, {overlayX: 'start', overlayY: 'top'}); strategy.withOffsetX(10); strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); let overlayRect = overlayElement.getBoundingClientRect(); expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect.top)); expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect.left + 10)); }); it('should position a panel with the y offset provided', () => { originRect = originElement.getBoundingClientRect(); strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'start', originY: 'top'}, {overlayX: 'start', overlayY: 'top'}); strategy.withOffsetY(50); strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); let overlayRect = overlayElement.getBoundingClientRect(); expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect.top + 50)); expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect.left)); }); it('should allow for the fallback positions to specify their own offsets', () => { originElement.style.bottom = '0'; originElement.style.left = '50%'; originElement.style.position = 'fixed'; originRect = originElement.getBoundingClientRect(); strategy = positionBuilder .connectedTo( fakeElementRef, {originX: 'start', originY: 'top'}, {overlayX: 'start', overlayY: 'top'}) .withFallbackPosition( {originX: 'start', originY: 'top'}, {overlayX: 'start', overlayY: 'bottom'}, -100, -100); strategy.withOffsetY(50).withOffsetY(50); strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); let overlayRect = overlayElement.getBoundingClientRect(); expect(Math.floor(overlayRect.bottom)).toBe(Math.floor(originRect.top - 100)); expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect.left - 100)); }); }); it('should emit onPositionChange event when position changes', () => { originElement.style.top = '200px'; originElement.style.right = '25px'; strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'end', originY: 'center'}, {overlayX: 'start', overlayY: 'center'}) .withFallbackPosition( {originX: 'start', originY: 'bottom'}, {overlayX: 'end', overlayY: 'top'}); const positionChangeHandler = jasmine.createSpy('positionChangeHandler'); const subscription = strategy.onPositionChange.subscribe(positionChangeHandler); strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); const latestCall = positionChangeHandler.calls.mostRecent(); expect(positionChangeHandler).toHaveBeenCalled(); expect(latestCall.args[0] instanceof ConnectedOverlayPositionChange) .toBe(true, `Expected strategy to emit an instance of ConnectedOverlayPositionChange.`); // If the strategy is re-applied and the initial position would now fit, // the position change event should be emitted again. originElement.style.top = '200px'; originElement.style.left = '200px'; strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); expect(positionChangeHandler).toHaveBeenCalledTimes(2); subscription.unsubscribe(); }); it('should emit the onPositionChange event even if none of the positions fit', () => { originElement.style.bottom = '25px'; originElement.style.right = '25px'; strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'end', originY: 'bottom'}, {overlayX: 'start', overlayY: 'top'}) .withFallbackPosition( {originX: 'start', originY: 'bottom'}, {overlayX: 'end', overlayY: 'top'}); const positionChangeHandler = jasmine.createSpy('positionChangeHandler'); const subscription = strategy.onPositionChange.subscribe(positionChangeHandler); strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); expect(positionChangeHandler).toHaveBeenCalled(); subscription.unsubscribe(); }); it('should pick the fallback position that shows the largest area of the element', () => { originElement.style.top = '200px'; originElement.style.right = '25px'; originRect = originElement.getBoundingClientRect(); strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'end', originY: 'center'}, {overlayX: 'start', overlayY: 'center'}) .withFallbackPosition( {originX: 'end', originY: 'top'}, {overlayX: 'start', overlayY: 'bottom'}) .withFallbackPosition( {originX: 'end', originY: 'top'}, {overlayX: 'end', overlayY: 'top'}); strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); let overlayRect = overlayElement.getBoundingClientRect(); expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect.top)); expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect.left)); }); it('should re-use the preferred position when re-applying while locked in', () => { strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'end', originY: 'center'}, {overlayX: 'start', overlayY: 'center'}) .withLockedPosition(true) .withFallbackPosition( {originX: 'start', originY: 'bottom'}, {overlayX: 'end', overlayY: 'top'}); const recalcSpy = spyOn(strategy, 'recalculateLastPosition'); strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); expect(recalcSpy).not.toHaveBeenCalled(); strategy.apply(); expect(recalcSpy).toHaveBeenCalled(); }); /** * Run all tests for connecting the overlay to the origin such that first preferred * position does not go off-screen. We do this because there are several cases where we * want to run the exact same tests with different preconditions (e.g., not scroll, scrolled, * different element sized, etc.). */ function runSimplePositionTests() { it('should position a panel below, left-aligned', () => { strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'start', originY: 'bottom'}, {overlayX: 'start', overlayY: 'top'}); strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); let overlayRect = overlayElement.getBoundingClientRect(); expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect!.bottom)); expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect!.left)); }); it('should position to the right, center aligned vertically', () => { strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'end', originY: 'center'}, {overlayX: 'start', overlayY: 'center'}); strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); let overlayRect = overlayElement.getBoundingClientRect(); expect(Math.floor(overlayRect.top)).toBe(Math.floor(originCenterY! - (OVERLAY_HEIGHT / 2))); expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect!.right)); }); it('should position to the left, below', () => { strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'start', originY: 'bottom'}, {overlayX: 'end', overlayY: 'top'}); strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); let overlayRect = overlayElement.getBoundingClientRect(); expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect!.bottom)); expect(Math.round(overlayRect.right)).toBe(Math.round(originRect!.left)); }); it('should position above, right aligned', () => { strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'end', originY: 'top'}, {overlayX: 'end', overlayY: 'bottom'}); strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); let overlayRect = overlayElement.getBoundingClientRect(); expect(Math.round(overlayRect.bottom)).toBe(Math.round(originRect!.top)); expect(Math.round(overlayRect.right)).toBe(Math.round(originRect!.right)); }); it('should position below, centered', () => { strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'center', originY: 'bottom'}, {overlayX: 'center', overlayY: 'top'}); strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); let overlayRect = overlayElement.getBoundingClientRect(); expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect!.bottom)); expect(Math.floor(overlayRect.left)).toBe(Math.floor(originCenterX! - (OVERLAY_WIDTH / 2))); }); it('should center the overlay on the origin', () => { strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'center', originY: 'center'}, {overlayX: 'center', overlayY: 'center'}); strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); let overlayRect = overlayElement.getBoundingClientRect(); expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect!.top)); expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect!.left)); }); } }); describe('onPositionChange with scrollable view properties', () => { let overlayElement: HTMLElement; let strategy: ConnectedPositionStrategy; let scrollable: HTMLDivElement; let positionChangeHandler: jasmine.Spy; let onPositionChangeSubscription: Subscription; let positionChange: ConnectedOverlayPositionChange; beforeEach(() => { // Set up the overlay overlayElement = createPositionedBlockElement(); overlayContainerElement.appendChild(overlayElement); // Set up the origin let originElement = createBlockElement(); originElement.style.margin = '0 1000px 1000px 0'; // Added so that the container scrolls // Create a scrollable container and put the origin inside scrollable = createOverflowContainerElement(); document.body.appendChild(scrollable); scrollable.appendChild(originElement); // Create a strategy with knowledge of the scrollable container let fakeElementRef = new ElementRef(originElement); strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'start', originY: 'bottom'}, {overlayX: 'start', overlayY: 'top'}); strategy.withScrollableContainers([ new CdkScrollable(new ElementRef(scrollable), null!, null!)]); strategy.attach(fakeOverlayRef(overlayElement)); positionChangeHandler = jasmine.createSpy('positionChangeHandler'); onPositionChangeSubscription = strategy.onPositionChange.subscribe(positionChangeHandler); }); afterEach(() => { onPositionChangeSubscription.unsubscribe(); document.body.removeChild(scrollable); }); it('should not have origin or overlay clipped or out of view without scroll', () => { strategy.apply(); expect(positionChangeHandler).toHaveBeenCalled(); positionChange = positionChangeHandler.calls.mostRecent().args[0]; expect(positionChange.scrollableViewProperties).toEqual({ isOriginClipped: false, isOriginOutsideView: false, isOverlayClipped: false, isOverlayOutsideView: false }); }); it('should evaluate if origin is clipped if scrolled slightly down', () => { scrollable.scrollTop = 10; // Clip the origin by 10 pixels strategy.apply(); expect(positionChangeHandler).toHaveBeenCalled(); positionChange = positionChangeHandler.calls.mostRecent().args[0]; expect(positionChange.scrollableViewProperties).toEqual({ isOriginClipped: true, isOriginOutsideView: false, isOverlayClipped: false, isOverlayOutsideView: false }); }); it('should evaluate if origin is out of view and overlay is clipped if scrolled enough', () => { scrollable.scrollTop = 31; // Origin is 30 pixels, move out of view and clip the overlay 1px strategy.apply(); expect(positionChangeHandler).toHaveBeenCalled(); positionChange = positionChangeHandler.calls.mostRecent().args[0]; expect(positionChange.scrollableViewProperties).toEqual({ isOriginClipped: true, isOriginOutsideView: true, isOverlayClipped: true, isOverlayOutsideView: false }); }); it('should evaluate the overlay and origin are both out of the view', () => { scrollable.scrollTop = 61; // Scroll by overlay height + origin height + 1px buffer strategy.apply(); expect(positionChangeHandler).toHaveBeenCalled(); positionChange = positionChangeHandler.calls.mostRecent().args[0]; expect(positionChange.scrollableViewProperties).toEqual({ isOriginClipped: true, isOriginOutsideView: true, isOverlayClipped: true, isOverlayOutsideView: true }); }); }); describe('positioning properties', () => { let originElement: HTMLElement; let overlayElement: HTMLElement; let strategy: ConnectedPositionStrategy; let fakeElementRef: ElementRef; beforeEach(() => { // The origin and overlay elements need to be in the document body in order to have geometry. originElement = createPositionedBlockElement(); overlayElement = createPositionedBlockElement(); document.body.appendChild(originElement); overlayContainerElement.appendChild(overlayElement); fakeElementRef = new ElementRef(originElement); }); afterEach(() => { document.body.removeChild(originElement); }); describe('in ltr', () => { it('should use `left` when positioning an element at the start', () => { strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'start', originY: 'top'}, {overlayX: 'start', overlayY: 'top'}); strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); expect(overlayElement.style.left).toBeTruthy(); expect(overlayElement.style.right).toBeFalsy(); }); it('should use `right` when positioning an element at the end', () => { strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'end', originY: 'top'}, {overlayX: 'end', overlayY: 'top'}); strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); expect(overlayElement.style.right).toBeTruthy(); expect(overlayElement.style.left).toBeFalsy(); }); }); describe('in rtl', () => { it('should use `right` when positioning an element at the start', () => { strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'start', originY: 'top'}, {overlayX: 'start', overlayY: 'top'} ) .withDirection('rtl'); strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); expect(overlayElement.style.right).toBeTruthy(); expect(overlayElement.style.left).toBeFalsy(); }); it('should use `left` when positioning an element at the end', () => { strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'end', originY: 'top'}, {overlayX: 'end', overlayY: 'top'} ).withDirection('rtl'); strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); expect(overlayElement.style.left).toBeTruthy(); expect(overlayElement.style.right).toBeFalsy(); }); }); describe('vertical', () => { it('should use `top` when positioning at element along the top', () => { strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'start', originY: 'top'}, {overlayX: 'start', overlayY: 'top'} ); strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); expect(overlayElement.style.top).toBeTruthy(); expect(overlayElement.style.bottom).toBeFalsy(); }); it('should use `bottom` when positioning at element along the bottom', () => { strategy = positionBuilder.connectedTo( fakeElementRef, {originX: 'start', originY: 'bottom'}, {overlayX: 'start', overlayY: 'bottom'} ); strategy.attach(fakeOverlayRef(overlayElement)); strategy.apply(); expect(overlayElement.style.bottom).toBeTruthy(); expect(overlayElement.style.top).toBeFalsy(); }); }); }); }); /** Creates an absolutely positioned, display: block element with a default size. */ function createPositionedBlockElement() { let element = createBlockElement(); element.style.position = 'absolute'; return element; } /** Creates a block element with a default size. */ function createBlockElement() { let element = document.createElement('div'); element.style.width = `${DEFAULT_WIDTH}px`; element.style.height = `${DEFAULT_HEIGHT}px`; element.style.backgroundColor = 'rebeccapurple'; element.style.zIndex = '100'; return element; } /** Creates an overflow container with a set height and width with margin. */ function createOverflowContainerElement() { let element = document.createElement('div'); element.style.position = 'relative'; element.style.overflow = 'auto'; element.style.height = '300px'; element.style.width = '300px'; element.style.margin = '100px'; return element; } function fakeOverlayRef(overlayElement: HTMLElement) { return {overlayElement} as OverlayRef; }