/
githubmirror
/
amphtml
Обзор
Документация
Войти
/
githubmirror
/
amphtml
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
test/unit/test-resource.js
2 100 строк
75 KB
Justin Ridgewell
Lint remaining static enums in src (#36821)
16 ноя 2021, 01:07
Не верифицирован
16 ноя 2021, 01:07
556e94a
Код
Авторство
О чём код?
import {LayoutPriority_Enum} from '#core/dom/layout'; import {layoutRectLtwh} from '#core/dom/layout/rect'; import {Services} from '#service'; import {AmpDocSingle} from '#service/ampdoc-impl'; import {OwnersImpl} from '#service/owners-impl'; import {Resource, ResourceState_Enum} from '#service/resource'; import {ResourcesImpl} from '#service/resources-impl'; import {isCancellation} from '../../src/error-reporting'; describes.realWin('Resource', {amp: true}, (env) => { let win, doc; let element; let elementMock; let resources; let resource; beforeEach(() => { win = env.win; doc = win.document; element = env.createAmpElement('amp-fake-element'); doc.body.appendChild(element); env.sandbox .stub(element, 'getLayoutPriority') .callsFake(() => LayoutPriority_Enum.ADS); elementMock = env.sandbox.mock(element); const viewer = Services.viewerForDoc(doc); env.sandbox.stub(viewer, 'isRuntimeOn').callsFake(() => false); env.sandbox .stub(ResourcesImpl.prototype, 'rebuildDomWhenReady_') .callsFake(() => {}); resources = new ResourcesImpl(env.ampdoc); resource = new Resource(1, element, resources); element.resources_ = resources; const vsync = Services.vsyncFor(win); env.sandbox.stub(vsync, 'mutate').callsFake((mutator) => { mutator(); }); resources.win = { document, getComputedStyle: (el) => { return el.fakeComputedStyle ? el.fakeComputedStyle : window.getComputedStyle(el); }, }; }); afterEach(() => { elementMock.verify(); }); it('should initialize correctly', () => { expect(resource.getId()).to.equal(1); expect(resource.debugid).to.equal('amp-fake-element#1'); expect(resource.getLayoutPriority()).to.equal(LayoutPriority_Enum.ADS); expect(resource.getState()).to.equal(ResourceState_Enum.NOT_BUILT); expect(resource.getLayoutBox().width).to.equal(0); expect(resource.getLayoutBox().height).to.equal(0); expect(resource.isInViewport()).to.equal(false); }); it('should initialize correctly when already built', () => { elementMock.expects('isBuilt').returns(true).once(); expect(new Resource(1, element, resources).getState()).to.equal( ResourceState_Enum.NOT_LAID_OUT ); }); it('should not build before upgraded', () => { elementMock.expects('isUpgraded').returns(false).atLeast(1); elementMock.expects('buildInternal').never(); elementMock.expects('updateLayoutBox').never(); expect(resource.build()).to.be.null; expect(resource.getState()).to.equal(ResourceState_Enum.NOT_BUILT); }); it('should build after upgraded', () => { const buildPromise = Promise.resolve(); elementMock.expects('isUpgraded').returns(true).atLeast(1); elementMock.expects('buildInternal').returns(buildPromise).once(); elementMock.expects('updateLayoutBox').never(); return resource.build().then(() => { expect(resource.getState()).to.equal(ResourceState_Enum.NOT_LAID_OUT); }); }); it('should build if element is currently building', () => { elementMock.expects('isBuilt').returns(false).once(); elementMock.expects('isBuilding').returns(true).once(); elementMock.expects('isUpgraded').returns(true).once(); elementMock.expects('buildInternal').returns(Promise.resolve()).once(); const r = new Resource(2, element, resources); expect(r.isBuilding()).to.be.true; }); it('should denylist on build failure', () => { env.sandbox .stub(resource, 'maybeReportErrorOnBuildFailure') .callsFake(() => {}); elementMock.expects('isUpgraded').returns(true).atLeast(1); elementMock .expects('buildInternal') .returns(Promise.reject(new Error('intentional'))) .once(); elementMock.expects('updateLayoutBox').never(); const buildPromise = resource.build(); expect(resource.isBuilding()).to.be.true; return buildPromise.then( () => { throw new Error('must have failed'); }, () => { expect(resource.isBuilding()).to.be.false; expect(resource.getState()).to.equal(ResourceState_Enum.NOT_BUILT); } ); }); it('should mark as not ready for layout even if already measured', () => { const box = layoutRectLtwh(0, 0, 100, 200); elementMock.expects('isUpgraded').returns(true).atLeast(1); elementMock.expects('buildInternal').returns(Promise.resolve()).once(); resource.layoutBox_ = box; return resource.build().then(() => { expect(resource.getState()).to.equal(ResourceState_Enum.NOT_LAID_OUT); }); }); it('should mark as not laid out if not yet measured', () => { elementMock.expects('isUpgraded').returns(true).atLeast(1); elementMock.expects('buildInternal').returns(Promise.resolve()).once(); return resource.build().then(() => { expect(resource.getState()).to.equal(ResourceState_Enum.NOT_LAID_OUT); }); }); it('should track size changes on measure', () => { elementMock.expects('isUpgraded').returns(true).atLeast(1); elementMock.expects('buildInternal').returns(Promise.resolve()).once(); return resource.build().then(() => { elementMock .expects('getBoundingClientRect') .returns({left: 11, top: 12, width: 111, height: 222}) .once(); elementMock .expects('updateLayoutBox') .withExactArgs( env.sandbox.match((data) => { return data.width == 111 && data.height == 222; }), true ) .once(); resource.measure(); }); }); it('should track no size changes on measure', () => { layoutRectLtwh(0, 0, 0, 0); elementMock.expects('isUpgraded').returns(true).atLeast(1); elementMock.expects('buildInternal').returns(Promise.resolve()).once(); return resource.build().then(() => { elementMock .expects('getBoundingClientRect') .returns({left: 0, top: 0, width: 0, height: 0}) .once(); elementMock .expects('updateLayoutBox') .withExactArgs( env.sandbox.match((data) => { return data.width == 0 && data.height == 0; }), false ) .once(); resource.measure(); }); }); it('should allow to measure when not upgraded', () => { elementMock.expects('isUpgraded').returns(false).atLeast(1); const viewport = Services.viewportForDoc(resource.element); env.sandbox .stub(viewport, 'getLayoutRect') .returns(layoutRectLtwh(0, 100, 300, 100)); env.sandbox.stub(viewport, 'isDeclaredFixed').returns(false); env.sandbox.stub(viewport, 'supportsPositionFixed').returns(true); expect(() => { resource.measure(); }).to.not.throw(); expect(resource.getLayoutBox()).to.eql(layoutRectLtwh(0, 100, 300, 100)); }); it('should allow measure even when not built', () => { elementMock.expects('isUpgraded').returns(true).atLeast(1); elementMock .expects('getBoundingClientRect') .returns(layoutRectLtwh(0, 0, 0, 0)) .once(); resource.measure(); expect(resource.getState()).to.equal(ResourceState_Enum.NOT_BUILT); expect(resource.isFixed()).to.be.false; }); it('should measure and update state', () => { elementMock.expects('isUpgraded').returns(true).atLeast(1); elementMock.expects('buildInternal').returns(Promise.resolve()).once(); return resource.build().then(() => { elementMock .expects('getBoundingClientRect') .returns({left: 11, top: 12, width: 111, height: 222}) .once(); elementMock .expects('updateLayoutBox') .withExactArgs( env.sandbox.match((data) => { return data.width == 111 && data.height == 222; }), true ) .once(); resource.measure(); expect(resource.getState()).to.equal(ResourceState_Enum.READY_FOR_LAYOUT); expect(resource.getLayoutBox().left).to.equal(11); expect(resource.getLayoutBox().top).to.equal(12); expect(resource.getLayoutBox().width).to.equal(111); expect(resource.getLayoutBox().height).to.equal(222); expect(resource.isFixed()).to.be.false; }); }); it('should update initial box only on first measure', () => { elementMock.expects('isUpgraded').returns(true).atLeast(1); elementMock.expects('buildInternal').returns(Promise.resolve()).once(); return resource.build().then(() => { element.getBoundingClientRect = () => ({ left: 11, top: 12, width: 111, height: 222, }); resource.measure(); expect(resource.getLayoutBox().top).to.equal(12); expect(resource.getInitialLayoutBox().top).to.equal(12); element.getBoundingClientRect = () => ({ left: 11, top: 22, width: 111, height: 222, }); resource.measure(); expect(resource.getLayoutBox().top).to.equal(22); expect(resource.getInitialLayoutBox().top).to.equal(12); }); }); it('should request measure even when not built', () => { expect(resource.isMeasureRequested()).to.be.false; elementMock.expects('getBoundingClientRect').never(); resource.requestMeasure(); expect(resource.isMeasureRequested()).to.be.true; }); it('should request measure when built', () => { expect(resource.isMeasureRequested()).to.be.false; elementMock.expects('getBoundingClientRect').never(); resource.state_ = ResourceState_Enum.READY_FOR_LAYOUT; resource.requestMeasure(); expect(resource.isMeasureRequested()).to.be.true; }); it('should always layout if has not been laid out before', () => { elementMock.expects('isUpgraded').returns(true).atLeast(1); resource.state_ = ResourceState_Enum.NOT_LAID_OUT; resource.layoutBox_ = {left: 11, top: 12, width: 111, height: 222}; elementMock .expects('getBoundingClientRect') .returns(resource.layoutBox_) .once(); resource.measure(); expect(resource.getState()).to.equal(ResourceState_Enum.READY_FOR_LAYOUT); }); it('should not relayout if has box has not changed', () => { resource.state_ = ResourceState_Enum.LAYOUT_COMPLETE; resource.layoutBox_ = {left: 11, top: 12, width: 111, height: 222}; // Left is not part of validation. elementMock .expects('getBoundingClientRect') .returns({left: 11 + 10, top: 12, width: 111, height: 222}) .once(); resource.measure(); expect(resource.getState()).to.equal(ResourceState_Enum.LAYOUT_COMPLETE); expect(resource.getLayoutBox().left).to.equal(11 + 10); }); it("should not relayout if box changed but element didn't opt in", () => { elementMock.expects('isUpgraded').returns(true).atLeast(1); resource.state_ = ResourceState_Enum.LAYOUT_COMPLETE; resource.layoutBox_ = {left: 11, top: 12, width: 111, height: 222}; // Width changed. elementMock .expects('getBoundingClientRect') .returns({left: 11, top: 12, width: 111 + 10, height: 222}) .once(); elementMock.expects('isRelayoutNeeded').returns(false).atLeast(1); resource.measure(); expect(resource.getState()).to.equal(ResourceState_Enum.LAYOUT_COMPLETE); expect(resource.getLayoutBox().width).to.equal(111 + 10); }); it('should relayout if box changed when element opted in', () => { elementMock.expects('isUpgraded').returns(true).atLeast(1); resource.state_ = ResourceState_Enum.LAYOUT_COMPLETE; resource.layoutBox_ = {left: 11, top: 12, width: 111, height: 222}; // Width changed. elementMock .expects('getBoundingClientRect') .returns({left: 11, top: 12, width: 111 + 10, height: 222}) .once(); elementMock.expects('isRelayoutNeeded').returns(true).atLeast(1); resource.measure(); expect(resource.getState()).to.equal(ResourceState_Enum.READY_FOR_LAYOUT); expect(resource.getLayoutBox().width).to.equal(111 + 10); }); it('should not relayout if element has not completed layout', () => { elementMock.expects('isUpgraded').returns(true).atLeast(1); resource.state_ = ResourceState_Enum.LAYOUT_SCHEDULED; resource.layoutBox_ = {left: 11, top: 12, width: 111, height: 222}; // Width changed. elementMock .expects('getBoundingClientRect') .returns({left: 11, top: 12, width: 111 + 10, height: 222}) .once(); elementMock.expects('isRelayoutNeeded').returns(true).atLeast(0); resource.measure(); expect(resource.getState()).to.equal(ResourceState_Enum.LAYOUT_SCHEDULED); expect(resource.getLayoutBox().width).to.equal(111 + 10); }); it('should calculate NOT fixed for non-displayed elements', () => { elementMock.expects('isUpgraded').returns(true).atLeast(1); elementMock .expects('getBoundingClientRect') .returns(layoutRectLtwh(0, 0, 0, 0)) .once(); element.isAlwaysFixed = () => true; resource.measure(); expect(resource.isFixed()).to.be.false; }); it('should calculate fixed for always-fixed parent', () => { elementMock.expects('isUpgraded').returns(true).atLeast(1); elementMock .expects('getBoundingClientRect') .returns(layoutRectLtwh(0, 0, 10, 10)) .once(); const viewport = Services.viewportForDoc(resource.element); env.sandbox.stub(viewport, 'getScrollTop').returns(11); env.sandbox.defineProperty(element, 'offsetParent', { value: { isAlwaysFixed: () => true, }, }); resource.measure(); expect(resource.isFixed()).to.be.true; expect(resource.getLayoutBox()).to.eql(layoutRectLtwh(0, 11, 10, 10)); }); it('should calculate fixed for fixed-style parent', () => { elementMock.expects('isUpgraded').returns(true).atLeast(1); elementMock .expects('getBoundingClientRect') .returns(layoutRectLtwh(0, 0, 10, 10)) .once(); const viewport = Services.viewportForDoc(resource.element); env.sandbox.stub(viewport, 'getScrollTop').returns(11); const fixedParent = doc.createElement('div'); fixedParent.style.position = 'fixed'; doc.body.appendChild(fixedParent); fixedParent.appendChild(element); env.sandbox.stub(viewport, 'isDeclaredFixed').callsFake((el) => { if (el == element) { return false; } if (el == fixedParent) { return true; } }); resource.measure(); expect(resource.isFixed()).to.be.true; expect(resource.getLayoutBox()).to.eql(layoutRectLtwh(0, 11, 10, 10)); }); describe('ensureMeasured', () => { it('should return layout box when the resource has NOT been measured', () => { env.sandbox.stub(element, 'isUpgraded').returns(true); env.sandbox .stub(element, 'getBoundingClientRect') .returns(layoutRectLtwh(0, 0, 10, 10)); return resource.ensureMeasured(); }); it('should return layout box when the resource has been measured', () => { env.sandbox.stub(element, 'isUpgraded').returns(true); env.sandbox .stub(element, 'getBoundingClientRect') .returns(layoutRectLtwh(0, 0, 10, 10)); resource.measure(); return resource.ensureMeasured(); }); }); describe('placeholder measure', () => { let rect; beforeEach(() => { element.setAttribute('placeholder', ''); env.sandbox.defineProperty(element, 'parentElement', { value: doc.createElement('amp-iframe'), configurable: true, writable: true, }); element.parentElement.__AMP__RESOURCE = {}; elementMock.expects('isUpgraded').returns(true).atLeast(1); elementMock.expects('buildInternal').returns(Promise.resolve()).once(); rect = {left: 11, top: 12, width: 111, height: 222}; resource = new Resource(1, element, resources); return resource.build(); }); it('should measure placeholder with stubbed parent', () => { elementMock.expects('getBoundingClientRect').returns(rect).once(); resource.measure(); expect(resource.getState()).to.equal(ResourceState_Enum.READY_FOR_LAYOUT); expect(resource.hasBeenMeasured()).to.be.true; }); it('should NOT measure placeholder with unstubbed parent', () => { // Parent is not stubbed yet, w/o __AMP__RESOURCE. delete element.parentElement.__AMP__RESOURCE; elementMock.expects('getBoundingClientRect').never(); resource.measure(); expect(resource.getState()).to.equal(ResourceState_Enum.NOT_LAID_OUT); expect(resource.hasBeenMeasured()).to.be.false; }); it('should support abnormal case with no parent', () => { delete element.parentElement; elementMock.expects('getBoundingClientRect').returns(rect).once(); resource.measure(); expect(resource.getState()).to.equal(ResourceState_Enum.READY_FOR_LAYOUT); expect(resource.hasBeenMeasured()).to.be.true; }); it('should support abnormal case with non-AMP parent', () => { element.parentElement = document.createElement('div'); elementMock.expects('getBoundingClientRect').returns(rect).once(); resource.measure(); expect(resource.getState()).to.equal(ResourceState_Enum.READY_FOR_LAYOUT); expect(resource.hasBeenMeasured()).to.be.true; }); }); it('should hide and update layout box on collapse', () => { resource.layoutBox_ = {left: 11, top: 12, width: 111, height: 222}; resource.isFixed_ = true; elementMock .expects('updateLayoutBox') .withExactArgs( env.sandbox.match((data) => { return data.width == 0 && data.height == 0; }) ) .once(); const owner = { collapsedCallback: env.sandbox.spy(), }; env.sandbox.stub(resource, 'getOwner').callsFake(() => { return owner; }); resource.completeCollapse(); expect(resource.element).to.have.attribute('hidden'); expect(resource.getLayoutBox().width).to.equal(0); expect(resource.getLayoutBox().height).to.equal(0); expect(resource.isFixed()).to.be.false; expect(owner.collapsedCallback).to.be.calledOnce; }); it('should show and request measure on expand', () => { resource.completeCollapse(); resource.layoutBox_ = {left: 11, top: 12, width: 0, height: 0}; resource.isFixed_ = false; resource.requestMeasure = env.sandbox.stub(); resource.completeExpand(); expect(resource.element).to.not.have.display('none'); expect(resource.requestMeasure).to.be.calledOnce; }); it('should ignore startLayout if already completed or failed or going', () => { elementMock.expects('layoutCallback').never(); resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 10}; resource.state_ = ResourceState_Enum.LAYOUT_COMPLETE; resource.startLayout(); resource.state_ = ResourceState_Enum.LAYOUT_FAILED; resource.startLayout(); resource.state_ = ResourceState_Enum.READY_FOR_LAYOUT; allowConsoleError(() => { resource.startLayout(); }); resource.state_ = ResourceState_Enum.LAYOUT_SCHEDULED; resource.layoutPromise_ = {}; resource.startLayout(); }); it('should fail startLayout if not built', () => { elementMock.expects('layoutCallback').never(); resource.state_ = ResourceState_Enum.NOT_BUILT; allowConsoleError(() => { expect(() => { resource.startLayout(); }).to.throw(/Not ready to start layout/); }); }); it('should ignore startLayout if not visible', () => { elementMock.expects('layoutCallback').never(); resource.state_ = ResourceState_Enum.LAYOUT_SCHEDULED; resource.layoutBox_ = {left: 11, top: 12, width: 0, height: 0}; allowConsoleError(() => { expect(() => { resource.startLayout(); }).to.throw(/Not displayed/); }); }); it('should force startLayout for first layout', () => { elementMock.expects('layoutCallback').returns(Promise.resolve()).once(); resource.state_ = ResourceState_Enum.LAYOUT_SCHEDULED; resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 10}; resource.startLayout(); expect(resource.getState()).to.equal(ResourceState_Enum.LAYOUT_SCHEDULED); }); it('should abort startLayout with unload', async () => { const neverEndingPromise = new Promise(() => {}); elementMock.expects('layoutCallback').returns(neverEndingPromise).once(); resource.state_ = ResourceState_Enum.LAYOUT_SCHEDULED; resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 10}; const layoutPromise = resource.startLayout(); expect(resource.getState()).to.equal(ResourceState_Enum.LAYOUT_SCHEDULED); resource.unload(); let error; try { await layoutPromise; } catch (e) { error = e; } expect(error).to.exist; expect(isCancellation(error)).to.be.true; }); it('should ignore startLayout for re-layout when not opt-in', () => { elementMock.expects('layoutCallback').never(); resource.state_ = ResourceState_Enum.LAYOUT_SCHEDULED; resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 10}; resource.layoutCount_ = 1; elementMock.expects('isRelayoutNeeded').returns(false).atLeast(1); resource.startLayout(); expect(resource.getState()).to.equal(ResourceState_Enum.LAYOUT_COMPLETE); }); it('should force startLayout for re-layout when opt-in', () => { elementMock.expects('layoutCallback').returns(Promise.resolve()).once(); resource.state_ = ResourceState_Enum.LAYOUT_SCHEDULED; resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 10}; resource.layoutCount_ = 1; elementMock.expects('isRelayoutNeeded').returns(true).atLeast(1); resource.startLayout(); expect(resource.getState()).to.equal(ResourceState_Enum.LAYOUT_SCHEDULED); }); it('should complete startLayout', () => { elementMock.expects('layoutCallback').returns(Promise.resolve()).once(); resource.state_ = ResourceState_Enum.LAYOUT_SCHEDULED; resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 10}; const loaded = resource.loadedOnce(); const promise = resource.startLayout(); expect(resource.layoutPromise_).to.not.equal(null); expect(resource.getState()).to.equal(ResourceState_Enum.LAYOUT_SCHEDULED); return promise.then(() => { expect(resource.getState()).to.equal(ResourceState_Enum.LAYOUT_COMPLETE); expect(resource.layoutPromise_).to.equal(null); return loaded; // Just making sure this doesn't time out. }); }); it('should complete startLayout with height == 0', () => { elementMock.expects('layoutCallback').returns(Promise.resolve()).once(); elementMock.expects('getLayout').returns('fluid').once(); resource.state_ = ResourceState_Enum.LAYOUT_SCHEDULED; resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 0}; const loaded = resource.loadedOnce(); const promise = resource.startLayout(); expect(resource.layoutPromise_).to.not.equal(null); expect(resource.getState()).to.equal(ResourceState_Enum.LAYOUT_SCHEDULED); return promise.then(() => { expect(resource.getState()).to.equal(ResourceState_Enum.LAYOUT_COMPLETE); expect(resource.layoutPromise_).to.equal(null); return loaded; }); }); it('should fail startLayout', () => { const error = new Error('intentional'); elementMock.expects('layoutCallback').returns(Promise.reject(error)).once(); resource.state_ = ResourceState_Enum.LAYOUT_SCHEDULED; resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 10}; const promise = resource.startLayout(); expect(resource.layoutPromise_).to.not.equal(null); expect(resource.getState()).to.equal(ResourceState_Enum.LAYOUT_SCHEDULED); /* global fail: false */ return promise .then( () => { fail('should not be here'); }, () => { expect(resource.getState()).to.equal( ResourceState_Enum.LAYOUT_FAILED ); expect(resource.layoutPromise_).to.equal(null); expect(resource.lastLayoutError_).to.equal(error); // Should fail with the same error again. return resource.startLayout(); } ) .then( () => { fail('should not be here'); }, (reason) => { expect(reason).to.equal(error); } ); }); it('should record layout schedule time', () => { resource.layoutScheduled(300); expect(resource.element.layoutScheduleTime).to.equal(300); // The time should be updated if scheduled multiple times. resource.layoutScheduled(400); expect(resource.element.layoutScheduleTime).to.equal(400); expect(resource.getState()).to.equal(ResourceState_Enum.LAYOUT_SCHEDULED); }); it('should not record layout schedule time in startLayout', () => { resource.state_ = ResourceState_Enum.LAYOUT_SCHEDULED; resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 10}; allowConsoleError(() => resource.startLayout()); expect(resource.element.layoutScheduleTime).to.be.undefined; expect(resource.getState()).to.equal(ResourceState_Enum.LAYOUT_SCHEDULED); }); it('should change size and update state', () => { expect(resource.isMeasureRequested()).to.be.false; resource.state_ = ResourceState_Enum.READY_FOR_LAYOUT; elementMock .expects('applySize') .withExactArgs(111, 222, {top: 1, right: 2, bottom: 3, left: 4}) .once(); resource.changeSize(111, 222, {top: 1, right: 2, bottom: 3, left: 4}); expect(resource.isMeasureRequested()).to.be.true; }); it('should change size but not state', () => { resource.state_ = ResourceState_Enum.NOT_BUILT; elementMock .expects('applySize') .withExactArgs(111, 222, {top: 1, right: 2, bottom: 3, left: 4}) .once(); resource.changeSize(111, 222, {top: 1, right: 2, bottom: 3, left: 4}); expect(resource.getState()).to.equal(ResourceState_Enum.NOT_BUILT); }); it('should update priority', () => { expect(resource.getLayoutPriority()).to.equal(LayoutPriority_Enum.ADS); resource.updateLayoutPriority(LayoutPriority_Enum.ADS); expect(resource.getLayoutPriority()).to.equal(LayoutPriority_Enum.ADS); resource.updateLayoutPriority(LayoutPriority_Enum.BACKGROUND); expect(resource.getLayoutPriority()).to.equal( LayoutPriority_Enum.BACKGROUND ); resource.updateLayoutPriority(LayoutPriority_Enum.METADATA); expect(resource.getLayoutPriority()).to.equal(LayoutPriority_Enum.METADATA); resource.updateLayoutPriority(LayoutPriority_Enum.CONTENT); expect(resource.getLayoutPriority()).to.equal(LayoutPriority_Enum.CONTENT); }); describe('setInViewport', () => { let resolveWithinViewportSpy; beforeEach( () => (resolveWithinViewportSpy = env.sandbox.spy( resource, 'resolveDeferredsWhenWithinViewports_' )) ); it('should set inViewport to true', () => { resource.setInViewport(true); expect(resource.isInViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); describe('Resource set/get ownership', () => { let child; let parentResource; let owners; let resources; let grandChild; beforeEach(() => { const parent = { ownerDocument: {defaultView: window}, tagName: 'PARENT', hasAttribute: () => false, isBuilt: () => false, isBuilding: () => false, contains: () => true, }; child = { ownerDocument: {defaultView: window}, tagName: 'CHILD', hasAttribute: () => false, isBuilt: () => false, isBuilding: () => false, contains: () => true, parentElement: parent, }; grandChild = { ownerDocument: {defaultView: window}, tagName: 'GRANDCHILD', hasAttribute: () => false, isBuilt: () => false, isBuilding: () => false, contains: () => true, getElementsByClassName: () => { return []; }, parentElement: child, }; parent.getElementsByClassName = () => { return [child, grandChild]; }; child.getElementsByClassName = () => { return [grandChild]; }; const ampdoc = new AmpDocSingle(window); resources = new ResourcesImpl(ampdoc); owners = new OwnersImpl(ampdoc); parentResource = new Resource(1, parent, resources); }); it('should set resource before Resource created for child element', () => { owners.setOwner(child, parentResource.element); const childResource = new Resource(1, child, resources); expect(childResource.getOwner()).to.equal(parentResource.element); }); it('should always get the lastest owner value', () => { const childResource = new Resource(1, child, resources); expect(childResource.getOwner()).to.be.null; owners.setOwner(childResource.element, parentResource.element); expect(childResource.owner_).to.equal(parentResource.element); expect(childResource.getOwner()).to.equal(parentResource.element); }); it('should remove cached value for grandchild', () => { const childResource = new Resource(1, child, resources); const grandChildResource = new Resource(1, grandChild, resources); expect(grandChildResource.getOwner()).to.be.null; owners.setOwner(childResource.element, parentResource.element); expect(childResource.getOwner()).to.equal(parentResource.element); expect(grandChildResource.getOwner()).to.equal(parentResource.element); }); it('should not change owner if it is set via setOwner', () => { const childResource = new Resource(1, child, resources); const grandChildResource = new Resource(1, grandChild, resources); owners.setOwner(grandChildResource.element, parentResource.element); expect(grandChildResource.getOwner()).to.equal(parentResource.element); owners.setOwner(childResource.element, parentResource.element); expect(grandChildResource.getOwner()).to.equal(parentResource.element); }); }); describe('unlayoutCallback', () => { it('should NOT call unlayoutCallback on unbuilt element', () => { resource.state_ = ResourceState_Enum.NOT_BUILT; elementMock.expects('unlayoutCallback').never(); resource.unlayout(); expect(resource.getState()).to.equal(ResourceState_Enum.NOT_BUILT); }); it('should call unlayoutCallback on built element and update state', () => { resource.state_ = ResourceState_Enum.LAYOUT_COMPLETE; elementMock.expects('unlayoutCallback').returns(true).once(); elementMock.expects('togglePlaceholder').withArgs(true).once(); resource.unlayout(); expect(resource.getState()).to.equal(ResourceState_Enum.NOT_LAID_OUT); }); it('updated state should bypass isRelayoutNeeded', () => { resource.state_ = ResourceState_Enum.LAYOUT_COMPLETE; elementMock.expects('unlayoutCallback').returns(true).once(); elementMock.expects('togglePlaceholder').withArgs(true).once(); elementMock.expects('isUpgraded').returns(true).atLeast(1); elementMock .expects('getBoundingClientRect') .returns({left: 1, top: 1, width: 1, height: 1}) .once(); resource.unlayout(); resource.state_ = ResourceState_Enum.LAYOUT_SCHEDULED; elementMock.expects('layoutCallback').returns(Promise.resolve()).once(); resource.measure(); resource.startLayout(); }); it('should call unlayoutCallback on built element but NOT update state', () => { resource.state_ = ResourceState_Enum.LAYOUT_COMPLETE; elementMock.expects('unlayoutCallback').returns(false).once(); elementMock.expects('togglePlaceholder').withArgs(true).never(); resource.unlayout(); expect(resource.getState()).to.equal(ResourceState_Enum.LAYOUT_COMPLETE); }); it('should delegate unload to unlayoutCallback', () => { resource.state_ = ResourceState_Enum.LAYOUT_COMPLETE; elementMock.expects('unlayoutCallback').returns(false).once(); elementMock.expects('togglePlaceholder').withArgs(true).never(); resource.unload(); expect(resource.getState()).to.equal(ResourceState_Enum.LAYOUT_COMPLETE); }); }); describe('pause', () => { it('should call pause on unbuilt element', () => { resource.state_ = ResourceState_Enum.NOT_BUILT; elementMock.expects('pause').once(); resource.pause(); }); it('should call pause on built element', () => { resource.state_ = ResourceState_Enum.LAYOUT_COMPLETE; elementMock.expects('pause').once(); resource.pause(); }); it('should NOT call unlayoutCallback', () => { resource.state_ = ResourceState_Enum.LAYOUT_COMPLETE; elementMock.expects('pause').once(); elementMock.expects('unlayoutCallback').never(); resource.pause(); }); describe('when remove from DOM', () => { it('should call pause on remove for unbuilt ele', () => { resource.state_ = ResourceState_Enum.NOT_BUILT; elementMock.expects('pause').once(); resource.pauseOnRemove(); }); it('should call pause on remove for built ele', () => { resource.state_ = ResourceState_Enum.LAYOUT_COMPLETE; elementMock.expects('pause').once(); resource.pauseOnRemove(); }); }); // TODO(jridgewell): unskip the tests. This fails when run alone. describe.skip('manual disconnect', () => { beforeEach(() => { element.setAttribute('layout', 'nodisplay'); doc.body.appendChild(element); resource = Resource.forElementOptional(element); resources = element.getResources(); }); it('should call disconnect on remove for built ele', () => { env.sandbox.stub(element, 'isConnected').value(false); const remove = env.sandbox.spy(resources, 'remove'); resource.disconnect(); expect(remove).to.have.been.called; expect(Resource.forElementOptional(resource.element)).to.not.exist; }); it('should call disconnected regardless of isConnected', () => { // element is already connected to DOM const spy = env.sandbox.spy(resources, 'remove'); resource.disconnect(); expect(spy).to.have.been.called; expect(Resource.forElementOptional(resource.element)).to.not.exist; }); }); }); describe('resume', () => { it('should call resume on unbuilt element', () => { resource.state_ = ResourceState_Enum.NOT_BUILT; elementMock.expects('resume').once(); resource.resume(); }); it('should call resume on un-paused element', () => { resource.state_ = ResourceState_Enum.LAYOUT_COMPLETE; elementMock.expects('resume').once(); resource.resume(); }); }); }); describes.sandboxed('Resource idleRenderOutsideViewport', {}, (env) => { let element; let resources; let resource; let idleRenderOutsideViewport; let isWithinViewportRatio; beforeEach(() => { idleRenderOutsideViewport = env.sandbox.stub(); element = { idleRenderOutsideViewport, ownerDocument: {defaultView: window}, tagName: 'AMP-AD', hasAttribute: () => false, isBuilt: () => false, isBuilding: () => false, isUpgraded: () => false, prerenderAllowed: () => false, renderOutsideViewport: () => true, build: () => false, getBoundingClientRect: () => null, updateLayoutBox: () => {}, isRelayoutNeeded: () => false, layoutCallback: () => {}, applySize: () => {}, unlayoutOnPause: () => false, unlayoutCallback: () => true, pause: () => false, resume: () => false, getLayoutPriority: () => LayoutPriority_Enum.CONTENT, }; resources = new ResourcesImpl(new AmpDocSingle(window)); resource = new Resource(1, element, resources); isWithinViewportRatio = env.sandbox.stub(resource, 'isWithinViewportRatio'); }); it('should return true if isWithinViewportRatio', () => { idleRenderOutsideViewport.returns(5); isWithinViewportRatio.withArgs(5).returns(true); expect(resource.idleRenderOutsideViewport()).to.equal(true); }); it('should return false for false element idleRenderOutsideViewport', () => { idleRenderOutsideViewport.returns(false); isWithinViewportRatio.withArgs(false).returns(false); expect(resource.idleRenderOutsideViewport()).to.equal(false); }); }); describes.realWin('Resource renderOutsideViewport', {amp: true}, (env) => { let element; let resources; let resource; let viewport; let renderOutsideViewport; let resolveWithinViewportSpy; beforeEach(() => { element = env.createAmpElement('amp-fake-element'); env.win.document.body.appendChild(element); resources = new ResourcesImpl(env.ampdoc); resource = new Resource(1, element, resources); viewport = Services.viewportForDoc(env.ampdoc); renderOutsideViewport = env.sandbox.stub(element, 'renderOutsideViewport'); env.sandbox .stub(viewport, 'getRect') .returns(layoutRectLtwh(0, 0, 100, 100)); resolveWithinViewportSpy = env.sandbox.spy( resource, 'resolveDeferredsWhenWithinViewports_' ); }); describe('boolean API', () => { describe('when element returns true', () => { beforeEach(() => { renderOutsideViewport.returns(true); }); describe('when element is inside viewport', () => { it('should allow rendering when bottom falls outside', () => { resource.layoutBox_ = layoutRectLtwh(0, 10, 100, 100); expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when top falls outside', () => { resource.layoutBox_ = layoutRectLtwh(0, -10, 100, 100); expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); describe('when element is owned', () => { beforeEach(() => { env.sandbox.stub(resource, 'hasOwner').callsFake(() => true); }); it('should allow rendering when bottom falls outside', () => { resource.layoutBox_ = layoutRectLtwh(0, 10, 100, 100); expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when top falls outside', () => { resource.layoutBox_ = layoutRectLtwh(0, -10, 100, 100); expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); }); describe('when element is just below viewport', () => { beforeEach(() => { resource.layoutBox_ = layoutRectLtwh(0, 110, 100, 100); }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); describe('when element is owned', () => { beforeEach(() => { env.sandbox.stub(resource, 'hasOwner').callsFake(() => true); }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); }); describe('when element is marginally below viewport', () => { beforeEach(() => { resource.layoutBox_ = layoutRectLtwh(0, 250, 100, 100); }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); describe('when element is owned', () => { beforeEach(() => { env.sandbox.stub(resource, 'hasOwner').callsFake(() => true); }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); }); describe('when element is wayyy below viewport', () => { beforeEach(() => { resource.layoutBox_ = layoutRectLtwh(0, 1000, 100, 100); }); it('should allow rendering', () => { expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); describe('when element is owned', () => { beforeEach(() => { env.sandbox.stub(resource, 'hasOwner').callsFake(() => true); }); it('should allow rendering', () => { expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); }); describe('when element is just above viewport', () => { beforeEach(() => { resource.layoutBox_ = layoutRectLtwh(0, -10, 100, 100); }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); describe('when element is owned', () => { beforeEach(() => { env.sandbox.stub(resource, 'hasOwner').callsFake(() => true); }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); }); describe('when element is marginally above viewport', () => { beforeEach(() => { resource.layoutBox_ = layoutRectLtwh(0, -250, 100, 100); }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); describe('when element is owned', () => { beforeEach(() => { env.sandbox.stub(resource, 'hasOwner').callsFake(() => true); }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); }); describe('when element is wayyy above viewport', () => { beforeEach(() => { resource.layoutBox_ = layoutRectLtwh(0, -1000, 100, 100); }); it('should allow rendering', () => { expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); describe('when element is owned', () => { beforeEach(() => { env.sandbox.stub(resource, 'hasOwner').callsFake(() => true); }); it('should allow rendering', () => { expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); }); }); describe('when element returns false', () => { beforeEach(() => { renderOutsideViewport.returns(false); }); describe('when element is inside viewport', () => { it('should allow rendering when bottom falls outside', () => { resource.layoutBox_ = layoutRectLtwh(0, 10, 100, 100); expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); it('should allow rendering when top falls outside', () => { resource.layoutBox_ = layoutRectLtwh(0, -10, 100, 100); expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); describe('when element is owned', () => { beforeEach(() => { env.sandbox.stub(resource, 'hasOwner').callsFake(() => true); }); it('should allow rendering when bottom falls outside', () => { resource.layoutBox_ = layoutRectLtwh(0, 10, 100, 100); expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when top falls outside', () => { resource.layoutBox_ = layoutRectLtwh(0, -10, 100, 100); expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); }); describe('when element is just below viewport', () => { beforeEach(() => { resource.layoutBox_ = layoutRectLtwh(0, 110, 100, 100); }); it('should disallow rendering when scrolling towards', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); it('should disallow rendering when scrolling away', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); describe('when element is owned', () => { beforeEach(() => { env.sandbox.stub(resource, 'hasOwner').callsFake(() => true); }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); }); describe('when element is marginally below viewport', () => { beforeEach(() => { resource.layoutBox_ = layoutRectLtwh(0, 250, 100, 100); }); it('should disallow rendering when scrolling towards', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); it('should disallow rendering when scrolling away', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); describe('when element is owned', () => { beforeEach(() => { env.sandbox.stub(resource, 'hasOwner').callsFake(() => true); }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); }); describe('when element is wayyy below viewport', () => { beforeEach(() => { resource.layoutBox_ = layoutRectLtwh(0, 1000, 100, 100); }); it('should disallow rendering', () => { expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); it('should disallow rendering when scrolling towards', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); it('should disallow rendering when scrolling away', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); describe('when element is owned', () => { beforeEach(() => { env.sandbox.stub(resource, 'hasOwner').callsFake(() => true); }); it('should allow rendering', () => { expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); }); describe('when element is just above viewport', () => { beforeEach(() => { resource.layoutBox_ = layoutRectLtwh(0, -10, 100, 100); }); it('should disallow rendering when scrolling towards', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); it('should disallow rendering when scrolling away', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); describe('when element is owned', () => { beforeEach(() => { env.sandbox.stub(resource, 'hasOwner').callsFake(() => true); }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); }); describe('when element is marginally above viewport', () => { beforeEach(() => { resource.layoutBox_ = layoutRectLtwh(0, -250, 100, 100); }); it('should disallow rendering when scrolling towards', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); it('should disallow rendering when scrolling away', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); describe('when element is owned', () => { beforeEach(() => { env.sandbox.stub(resource, 'hasOwner').callsFake(() => true); }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); }); describe('when element is wayyy above viewport', () => { beforeEach(() => { resource.layoutBox_ = layoutRectLtwh(0, -1000, 100, 100); }); it('should disallow rendering', () => { expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); it('should disallow rendering when scrolling towards', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); it('should disallow rendering when scrolling away', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); describe('when element is owned', () => { beforeEach(() => { env.sandbox.stub(resource, 'hasOwner').callsFake(() => true); }); it('should allow rendering', () => { expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); }); }); }); describe('number API', () => { beforeEach(() => { renderOutsideViewport.returns(3); }); describe('when element is inside viewport', () => { it('should allow rendering when bottom falls outside', () => { resource.layoutBox_ = layoutRectLtwh(0, 10, 100, 100); expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when top falls outside', () => { resource.layoutBox_ = layoutRectLtwh(0, -10, 100, 100); expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); describe('when element is owned', () => { beforeEach(() => { env.sandbox.stub(resource, 'hasOwner').callsFake(() => true); }); it('should allow rendering when bottom falls outside', () => { resource.layoutBox_ = layoutRectLtwh(0, 10, 100, 100); expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when top falls outside', () => { resource.layoutBox_ = layoutRectLtwh(0, -10, 100, 100); expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); }); describe('when element is just below viewport', () => { beforeEach(() => { resource.layoutBox_ = layoutRectLtwh(0, 110, 100, 100); }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); describe('when element is owned', () => { beforeEach(() => { env.sandbox.stub(resource, 'hasOwner').callsFake(() => true); }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); }); describe('when element is marginally below viewport', () => { beforeEach(() => { resource.layoutBox_ = layoutRectLtwh(0, 250, 100, 100); }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should disallow rendering when scrolling away', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); describe('when element is owned', () => { beforeEach(() => { env.sandbox.stub(resource, 'hasOwner').callsFake(() => true); }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); }); describe('when element is wayyy below viewport', () => { beforeEach(() => { resource.layoutBox_ = layoutRectLtwh(0, 1000, 100, 100); }); it('should disallow rendering', () => { expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); it('should disallow rendering when scrolling towards', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); it('should disallow rendering when scrolling away', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); describe('when element is owned', () => { beforeEach(() => { env.sandbox.stub(resource, 'hasOwner').callsFake(() => true); }); it('should allow rendering', () => { expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); }); describe('when element is just above viewport', () => { beforeEach(() => { resource.layoutBox_ = layoutRectLtwh(0, -10, 100, 100); }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); describe('when element is owned', () => { beforeEach(() => { env.sandbox.stub(resource, 'hasOwner').callsFake(() => true); }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); }); describe('when element is marginally above viewport', () => { beforeEach(() => { resource.layoutBox_ = layoutRectLtwh(0, -250, 100, 100); }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should disallow rendering when scrolling away', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); describe('when element is owned', () => { beforeEach(() => { env.sandbox.stub(resource, 'hasOwner').callsFake(() => true); }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); }); describe('when element is wayyy above viewport', () => { beforeEach(() => { resource.layoutBox_ = layoutRectLtwh(0, -1000, 100, 100); }); it('should disallow rendering', () => { expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); it('should disallow rendering when scrolling towards', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); it('should disallow rendering when scrolling away', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); describe('when element is owned', () => { beforeEach(() => { env.sandbox.stub(resource, 'hasOwner').callsFake(() => true); }); it('should allow rendering', () => { expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); }); describe('when element is on the left of viewport', () => { beforeEach(() => { resource.layoutBox_ = layoutRectLtwh(-200, 0, 100, 100); }); it('should disallow rendering', () => { expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); it('should disallow rendering when scrolling towards on y-axis', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); it('should disallow rendering when scrolling away on y-axis', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); describe('when element is owned', () => { beforeEach(() => { env.sandbox.stub(resource, 'hasOwner').callsFake(() => true); }); it('should allow rendering', () => { expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling towards on y-axis', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away on y-axis', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); }); describe('when element is on the right of viewport', () => { beforeEach(() => { resource.layoutBox_ = layoutRectLtwh(200, 0, 100, 100); }); it('should disallow rendering', () => { expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); it('should disallow rendering when scrolling towards on y-axis', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); it('should disallow rendering when scrolling away on y-axis', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(false); expect(resolveWithinViewportSpy).to.be.called; }); describe('when element is owned', () => { beforeEach(() => { env.sandbox.stub(resource, 'hasOwner').callsFake(() => true); }); it('should allow rendering', () => { expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling towards on y-axis', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away on y-axis', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); }); describe('when element is fully in viewport', () => { beforeEach(() => { resource.layoutBox_ = layoutRectLtwh(0, 0, 100, 100); }); it('should allow rendering', () => { expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); describe('when element is owned', () => { beforeEach(() => { env.sandbox.stub(resource, 'hasOwner').callsFake(() => true); }); it('should allow rendering', () => { expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling towards on y-axis', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away on y-axis', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); }); describe('when element is partially in viewport', () => { beforeEach(() => { resource.layoutBox_ = layoutRectLtwh(-50, -50, 100, 100); }); it('should allow rendering', () => { expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling towards', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); describe('when element is owned', () => { beforeEach(() => { env.sandbox.stub(resource, 'hasOwner').callsFake(() => true); }); it('should allow rendering', () => { expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling towards on y-axis', () => { resources.lastVelocity_ = -2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); it('should allow rendering when scrolling away on y-axis', () => { resources.lastVelocity_ = 2; expect(resource.renderOutsideViewport()).to.equal(true); expect(resolveWithinViewportSpy).to.be.calledOnce; }); }); }); }); describe('whenWithinViewport', () => { it('should resolve correctly', () => { env.sandbox .stub(resource, 'isWithinViewportRatio') .withArgs(3) .onCall(0) .returns(false) .onCall(1) .returns(false) .onCall(2) .returns(true) .onCall(3) .callsFake(() => { throw new Error('should not call!'); }); const promise = resource.whenWithinViewport(3); // Multiple calls should return the same promise. expect(resource.whenWithinViewport(3)).to.equal(promise); expect(Object.keys(resource.withViewportDeferreds_)).to.jsonEqual(['3']); // Call again should do nothing. resource.resolveDeferredsWhenWithinViewports_(); resource.resolveDeferredsWhenWithinViewports_(); return promise; }); it('should resolve immediately if already laid out', () => { env.sandbox.stub(resource, 'isLayoutPending').returns(false); return resource.whenWithinViewport(); }); it('should resolve correctly with float', () => { const isWithinViewportRatioStub = env.sandbox.stub( resource, 'isWithinViewportRatio' ); const ratio = {}; env.sandbox.stub(resource, 'getDistanceViewportRatio').returns(ratio); isWithinViewportRatioStub.withArgs(1.25).returns(false); isWithinViewportRatioStub.withArgs(1.25, ratio).returns(true); const promise = resource.whenWithinViewport(1.25); resource.resolveDeferredsWhenWithinViewports_(); return promise; }); }); });