text:
import {Resource, ResourceState_, Resources, TaskQueue_} from
    '../../src/service/resources-impl';
import {layoutRectLtwh} from '../../src/layout-rect';
import * as sinon from 'sinon';

/*eslint "google-camelcase/google-camelcase": 0*/
describe('Resources', () => {

  let sandbox;
  let clock;
  let resources;

  beforeEach(() => {
    sandbox = sinon.sandbox.create();
    clock = sandbox.useFakeTimers();
    resources = new Resources(window);
  });

  afterEach(() => {
    resources = null;
    clock = null;
    sandbox.restore();
    sandbox = null;
  });

  it('should calculate correct calcTaskScore', () => {
    const viewportRect = layoutRectLtwh(0, 100, 300, 400);
    // Task 1 is right in the middle of the viewport and priority 0
    const task_vp0_p0 = {
      resource: {
        getLayoutBox() {
          return layoutRectLtwh(0, 100, 300, 100);
        }
      },
      priority: 0
    };
    // Task 2 is in the viewport and priority 1
    const task_vp0_p1 = {
      resource: {
        getLayoutBox() {
          return layoutRectLtwh(0, 100, 300, 100);
        }
      },
      priority: 1
    };
    // Task 3 is above viewport and priority 0
    const task_vpu_p0 = {
      resource: {
        getLayoutBox() {
          return layoutRectLtwh(0, 0, 300, 50);
        }
      },
      priority: 0
    };
    // Task 4 is above viewport and priority 0
    const task_vpu_p1 = {
      resource: {
        getLayoutBox() {
          return layoutRectLtwh(0, 0, 300, 50);
        }
      },
      priority: 1
    };
    // Task 5 is below viewport and priority 0
    const task_vpd_p0 = {
      resource: {
        getLayoutBox() {
          return layoutRectLtwh(0, 600, 300, 50);
        }
      },
      priority: 0
    };
    // Task 6 is below viewport and priority 0
    const task_vpd_p1 = {
      resource: {
        getLayoutBox() {
          return layoutRectLtwh(0, 600, 300, 50);
        }
      },
      priority: 1
    };

    expect(resources.calcTaskScore_(viewportRect, 0, task_vp0_p0)).to.equal(0);
    expect(resources.calcTaskScore_(viewportRect, 0, task_vp0_p1)).to.equal(10);

    // +2 for "one viewport away" * 2 because dir is opposite
    expect(resources.calcTaskScore_(viewportRect, 0, task_vpu_p0)).to.equal(2);
    expect(resources.calcTaskScore_(viewportRect, 0, task_vpu_p1)).to.equal(12);

    // +1 for "one viewport away" * 1 because dir is the same
    expect(resources.calcTaskScore_(viewportRect, 0, task_vpd_p0)).to.equal(1);
    expect(resources.calcTaskScore_(viewportRect, 0, task_vpd_p1)).to.equal(11);
  });

  it('should calculate correct calcTaskTimeout', () => {
    // Task 1 is priority 0
    const task_p0 = {
      priority: 0,
      startTime: 0
    };
    // Task 2 is priority 1
    const task_p1 = {
      priority: 1,
      startTime: 0
    };

    // Empty pool
    expect(resources.calcTaskTimeout_(task_p0)).to.equal(0);
    expect(resources.calcTaskTimeout_(task_p1)).to.equal(0);

    // Hight priority task in pool
    resources.exec_.tasks_ = [task_p0];
    expect(resources.calcTaskTimeout_(task_p0)).to.equal(0);
    expect(resources.calcTaskTimeout_(task_p1)).to.equal(1000);

    clock.tick(100);
    expect(resources.calcTaskTimeout_(task_p1)).to.equal(900);

    clock.tick(1000);
    expect(resources.calcTaskTimeout_(task_p1)).to.equal(0);

    // Lower priority task in pool
    resources.exec_.tasks_ = [task_p1];
    expect(resources.calcTaskTimeout_(task_p0)).to.equal(0);
    expect(resources.calcTaskTimeout_(task_p1)).to.equal(0);

    // Multiple tasks in queue - maximum is picked
    task_p0.startTime = 1100;
    resources.exec_.tasks_ = [task_p0, task_p1];
    expect(resources.calcTaskTimeout_(task_p0)).to.equal(0);
    expect(resources.calcTaskTimeout_(task_p1)).to.equal(1000);
  });

  it('should not schedule non-prerenderable resource when' +
        ' document is hidden', () => {
    const resource = {
      getState: () => ResourceState_.READY_FOR_LAYOUT,
      isDisplayed: () => true,
      isInViewport: () => true,
      prerenderAllowed: () => false,
      renderOutsideViewport: () => false,
      startLayout: () => {}
    };
    resources.visible_ = false;
    resources.scheduleLayoutOrPreload_(resource, true);
    expect(resources.queue_.getSize()).to.equal(0);
  });

  it('should schedule prerenderable resource when' +
        ' document is hidden', () => {
    const resource = {
      getState: () => ResourceState_.READY_FOR_LAYOUT,
      isDisplayed: () => true,
      isInViewport: () => true,
      prerenderAllowed: () => true,
      renderOutsideViewport: () => true,
      getPriority: () => 1,
      startLayout: () => {},
      layoutScheduled: () => {}
    };
    resources.visible_ = false;
    resources.scheduleLayoutOrPreload_(resource, true);
    expect(resources.queue_.getSize()).to.equal(1);
  });

  it('should not schedule non-renderOutsideViewport resource when' +
        ' resource is not visible', () => {
    const resource = {
      getState: () => ResourceState_.READY_FOR_LAYOUT,
      isDisplayed: () => true,
      isInViewport: () => false,
      prerenderAllowed: () => true,
      renderOutsideViewport: () => false,
      startLayout: () => {},
    };
    resources.scheduleLayoutOrPreload_(resource, true);
    expect(resources.queue_.getSize()).to.equal(0);
  });

  it('should schedule renderOutsideViewport resource when' +
        ' resource is not visible', () => {
    const resource = {
      getState: () => ResourceState_.READY_FOR_LAYOUT,
      isDisplayed: () => true,
      isInViewport: () => false,
      prerenderAllowed: () => true,
      renderOutsideViewport: () => true,
      getPriority: () => 1,
      startLayout: () => {},
      layoutScheduled: () => {}
    };
    resources.scheduleLayoutOrPreload_(resource, true);
    expect(resources.queue_.getSize()).to.equal(1);
  });
});

describe('Resources discoverWork', () => {

  function createElement(rect) {
    return {
      tagName: 'amp-test',
      isBuilt: () => {
        return true;
      },
      isUpgraded: () => {
        return true;
      },
      getAttribute: () => {
        return null;
      },
      getBoundingClientRect: () => rect,
      updateLayoutBox: () => {},
      applySizesAndMediaQuery: () => {},
      layoutCallback: () => Promise.resolve(),
      viewportCallback: sinon.spy(),
      prerenderAllowed: () => true,
      renderOutsideViewport: () => true,
      isRelayoutNeeded: () => true,
    };
  }

  function createResource(id, rect) {
    const resource = new Resource(id, createElement(rect), resources);
    resource.state_ = ResourceState_.READY_FOR_LAYOUT;
    resource.layoutBox_ = rect;
    return resource;
  }

  let sandbox;
  let viewportMock;
  let resources;
  let resource1, resource2;

  beforeEach(() => {
    sandbox = sinon.sandbox.create();
    resources = new Resources(window);
    viewportMock = sandbox.mock(resources.viewport_);

    resource1 = createResource(1, layoutRectLtwh(10, 10, 100, 100));
    resource2 = createResource(2, layoutRectLtwh(10, 1010, 100, 100));
    resources.resources_ = [resource1, resource2];
    resources.vsync_ = {
      mutate: callback => callback()
    };
  });

  afterEach(() => {
    viewportMock.verify();
    viewportMock = null;
    resources = null;
    sandbox.restore();
    sandbox = null;
  });

  it('should render two screens when visible', () => {
    resources.visible_ = true;
    viewportMock.expects('getRect').returns(
        layoutRectLtwh(0, 0, 300, 400)).once();

    resources.discoverWork_();

    expect(resources.queue_.getSize()).to.equal(2);
    expect(resources.queue_.tasks_[0].resource).to.equal(resource1);
    expect(resources.queue_.tasks_[1].resource).to.equal(resource2);
  });

  it('should NOT rerender anything', () => {
    resource1.state_ = ResourceState_.LAYOUT_COMPLETE;
    resource2.state_ = ResourceState_.LAYOUT_COMPLETE;
    resources.visible_ = true;
    viewportMock.expects('getRect').returns(
        layoutRectLtwh(0, 0, 300, 400)).once();

    resources.discoverWork_();

    expect(resources.queue_.getSize()).to.equal(0);
  });

  it('should re-render from requested position', () => {
    resource1.state_ = ResourceState_.LAYOUT_COMPLETE;
    resource2.state_ = ResourceState_.LAYOUT_COMPLETE;
    resource1.element.getBoundingClientRect =
        () => layoutRectLtwh(10, 10, 100, 101);
    resource2.element.getBoundingClientRect =
        () => layoutRectLtwh(10, 1010, 100, 101);
    resources.visible_ = true;
    resources.relayoutAll_ = false;
    resources.relayoutTop_ = 1000;
    viewportMock.expects('getRect').returns(
        layoutRectLtwh(0, 0, 300, 400)).once();

    resources.discoverWork_();

    expect(resources.relayoutTop_).to.equal(-1);
    expect(resources.queue_.getSize()).to.equal(1);
    expect(resources.queue_.tasks_[0].resource).to.equal(resource2);
    expect(resource1.state_).to.equal(ResourceState_.LAYOUT_COMPLETE);
    expect(resource2.state_).to.equal(ResourceState_.LAYOUT_SCHEDULED);
  });

  it('should prerender only one screen with prerenderSize = 1', () => {
    resources.visible_ = false;
    resources.prerenderSize_ = 1;
    viewportMock.expects('getRect').returns(
        layoutRectLtwh(0, 0, 300, 1009)).once();

    resources.discoverWork_();

    expect(resources.queue_.getSize()).to.equal(1);
    expect(resources.queue_.tasks_[0].resource).to.equal(resource1);
  });

  it('should NOT prerender anything with prerenderSize = 0', () => {
    resources.visible_ = false;
    resources.prerenderSize_ = 0;
    viewportMock.expects('getRect').returns(
        layoutRectLtwh(0, 0, 300, 400)).once();

    resources.discoverWork_();

    expect(resources.queue_.getSize()).to.equal(0);
  });

  it('should remeasure when requested and scheduled unloads', () => {
    resource1.state_ = ResourceState_.LAYOUT_COMPLETE;
    resource2.state_ = ResourceState_.LAYOUT_COMPLETE;
    resources.visible_ = true;
    viewportMock.expects('getRect').returns(
        layoutRectLtwh(0, 0, 300, 400)).atLeast(1);

    const resource1MeasureStub = sandbox.stub(resource1, 'measure',
        resource1.measure.bind(resource1));
    const resource1UnloadStub = sandbox.stub(resource1, 'unload');
    const resource2MeasureStub = sandbox.stub(resource2, 'measure',
        resource2.measure.bind(resource2));
    const resource2UnloadStub = sandbox.stub(resource2, 'unload');

    // 1st pass: measure for the first time.
    resources.discoverWork_();
    expect(resource1MeasureStub.callCount).to.equal(1);
    expect(resource1UnloadStub.callCount).to.equal(0);
    expect(resource2MeasureStub.callCount).to.equal(1);
    expect(resource2UnloadStub.callCount).to.equal(0);

    // 2nd pass: do not remeasure anything.
    resources.discoverWork_();
    expect(resource1MeasureStub.callCount).to.equal(1);
    expect(resource1UnloadStub.callCount).to.equal(0);
    expect(resource2MeasureStub.callCount).to.equal(1);
    expect(resource2UnloadStub.callCount).to.equal(0);

    // 3rd pass: request remeasures and an unload.
    resource1.requestMeasure();
    resource2.requestMeasure();
    expect(resource1.isMeasureRequested()).to.be.true;
    expect(resource2.isMeasureRequested()).to.be.true;
    resource2.element.getBoundingClientRect =
        () => layoutRectLtwh(0, 0, 0, 0);  // Equiv to display:none.
    resources.discoverWork_();
    expect(resource1MeasureStub.callCount).to.equal(2);
    expect(resource1UnloadStub.callCount).to.equal(0);
    expect(resource2MeasureStub.callCount).to.equal(2);
    expect(resource2UnloadStub.callCount).to.equal(1);
  });
});

describe('Resources changeHeight', () => {

  function createElement(rect) {
    return {
      tagName: 'amp-test',
      isBuilt: () => {
        return true;
      },
      isUpgraded: () => {
        return true;
      },
      getAttribute: () => {
        return null;
      },
      getBoundingClientRect: () => rect,
      applySizesAndMediaQuery: () => {},
      layoutCallback: () => Promise.resolve(),
      viewportCallback: sinon.spy(),
      prerenderAllowed: () => true,
      renderOutsideViewport: () => false,
      isRelayoutNeeded: () => true,
      contains: unused_otherElement => false,
      updateLayoutBox: () => {},
      overflowCallback: (unused_overflown, unused_requestedHeight) => {},
    };
  }

  function createResource(id, rect) {
    const resource = new Resource(id, createElement(rect), resources);
    resource.element['__AMP__RESOURCE'] = resource;
    resource.state_ = ResourceState_.READY_FOR_LAYOUT;
    resource.layoutBox_ = rect;
    resource.changeHeight = sinon.spy();
    return resource;
  }

  let sandbox;
  let clock;
  let viewportMock;
  let resources;
  let resource1, resource2;

  beforeEach(() => {
    sandbox = sinon.sandbox.create();
    clock = sandbox.useFakeTimers();
    resources = new Resources(window);
    viewportMock = sandbox.mock(resources.viewport_);

    resource1 = createResource(1, layoutRectLtwh(10, 10, 100, 100));
    resource2 = createResource(2, layoutRectLtwh(10, 1010, 100, 100));
    resources.resources_ = [resource1, resource2];
  });

  afterEach(() => {
    viewportMock.verify();
    viewportMock = null;
    resources = null;
    clock = null;
    sandbox.restore();
    sandbox = null;
  });

  it('should schedule separate requests', () => {
    resources.scheduleChangeHeight_(resource1, 111, false);
    resources.scheduleChangeHeight_(resource2, 222, true);

    expect(resources.requestsChangeHeight_.length).to.equal(2);
    expect(resources.requestsChangeHeight_[0].resource).to.equal(resource1);
    expect(resources.requestsChangeHeight_[0].newHeight).to.equal(111);
    expect(resources.requestsChangeHeight_[0].force).to.equal(false);

    expect(resources.requestsChangeHeight_[1].resource).to.equal(resource2);
    expect(resources.requestsChangeHeight_[1].newHeight).to.equal(222);
    expect(resources.requestsChangeHeight_[1].force).to.equal(true);
  });

  it('should only schedule latest request for the same resource', () => {
    resources.scheduleChangeHeight_(resource1, 111, true);
    resources.scheduleChangeHeight_(resource1, 222, false);

    expect(resources.requestsChangeHeight_.length).to.equal(1);
    expect(resources.requestsChangeHeight_[0].resource).to.equal(resource1);
    expect(resources.requestsChangeHeight_[0].newHeight).to.equal(222);
    expect(resources.requestsChangeHeight_[0].force).to.equal(true);
  });

  it('should NOT change height if it didn\'t change', () => {
    resources.scheduleChangeHeight_(resource1, 100, true);
    resources.mutateWork_();
    expect(resources.relayoutTop_).to.equal(-1);
    expect(resources.requestsChangeHeight_.length).to.equal(0);
    expect(resource1.changeHeight.callCount).to.equal(0);
  });

  it('should change height', () => {
    resources.scheduleChangeHeight_(resource1, 111, true);
    resources.mutateWork_();
    expect(resources.relayoutTop_).to.equal(resource1.layoutBox_.top);
    expect(resources.requestsChangeHeight_.length).to.equal(0);
    expect(resource1.changeHeight.callCount).to.equal(1);
    expect(resource1.changeHeight.firstCall.args[0]).to.equal(111);
  });

  it('should pick the smallest relayoutTop', () => {
    resources.scheduleChangeHeight_(resource2, 111, true);
    resources.scheduleChangeHeight_(resource1, 111, true);
    resources.mutateWork_();
    expect(resources.relayoutTop_).to.equal(resource1.layoutBox_.top);
  });

  describe('attemptChangeHeight rules when element is in viewport', () => {
    let overflowCallbackSpy;
    let vsyncSpy;

    beforeEach(() => {
      overflowCallbackSpy = sinon.spy();
      resource1.element.overflowCallback = overflowCallbackSpy;
      viewportMock.expects('getRect').returns(
          {top: 0, left: 0, right: 100, bottom: 200, height: 200}).atLeast(1);
      viewportMock.expects('getScrollHeight').returns(10000).atLeast(1);
      resource1.layoutBox_ = {top: 10, left: 0, right: 100, bottom: 50,
          height: 50};
      vsyncSpy = sandbox.stub(resources.vsync_, 'run');
    });

    it('should NOT change height and calls overflowCallback', () => {
      resources.scheduleChangeHeight_(resource1, 111, false);
      resources.mutateWork_();
      expect(resources.requestsChangeHeight_.length).to.equal(0);
      expect(resource1.changeHeight.callCount).to.equal(0);
      expect(overflowCallbackSpy.callCount).to.equal(1);
      expect(overflowCallbackSpy.firstCall.args[0]).to.equal(true);
      expect(overflowCallbackSpy.firstCall.args[1]).to.equal(111);
      expect(resource1.getPendingChangeHeight()).to.equal(111);
    });

    it('should change height when new height is lower', () => {
      resources.scheduleChangeHeight_(resource1, 10, false);
      resources.mutateWork_();
      expect(resources.requestsChangeHeight_.length).to.equal(0);
      expect(resource1.changeHeight.callCount).to.equal(0);
      expect(overflowCallbackSpy.callCount).to.equal(0);
    });

    it('should change height when forced', () => {
      resources.scheduleChangeHeight_(resource1, 111, true);
      resources.mutateWork_();
      expect(resources.requestsChangeHeight_.length).to.equal(0);
      expect(resource1.changeHeight.callCount).to.equal(1);
      expect(overflowCallbackSpy.callCount).to.equal(1);
      expect(overflowCallbackSpy.firstCall.args[0]).to.equal(false);
    });

    it('should change height when document is invisible', () => {
      resources.visible_ = false;
      resources.scheduleChangeHeight_(resource1, 111, false);
      resources.mutateWork_();
      expect(resources.requestsChangeHeight_.length).to.equal(0);
      expect(resource1.changeHeight.callCount).to.equal(1);
      expect(overflowCallbackSpy.callCount).to.equal(1);
      expect(overflowCallbackSpy.firstCall.args[0]).to.equal(false);
    });

    it('should change height when active', () => {
      resource1.element.contains = () => true;
      resources.scheduleChangeHeight_(resource1, 111, false);
      resources.mutateWork_();
      expect(resources.requestsChangeHeight_.length).to.equal(0);
      expect(resource1.changeHeight.callCount).to.equal(1);
      expect(overflowCallbackSpy.callCount).to.equal(1);
      expect(overflowCallbackSpy.firstCall.args[0]).to.equal(false);
    });

    it('should change height when below the viewport', () => {
      resource1.layoutBox_ = {top: 10, left: 0, right: 100, bottom: 1050,
          height: 50};
      resources.scheduleChangeHeight_(resource1, 111, false);
      resources.mutateWork_();
      expect(resources.requestsChangeHeight_.length).to.equal(0);
      expect(resource1.changeHeight.callCount).to.equal(1);
      expect(overflowCallbackSpy.callCount).to.equal(1);
      expect(overflowCallbackSpy.firstCall.args[0]).to.equal(false);
    });

    it('should change height when slightly above the viewport', () => {
      resource1.layoutBox_ = {top: 10, left: 0, right: 100, bottom: 190,
          height: 50};
      resources.scheduleChangeHeight_(resource1, 111, false);
      resources.mutateWork_();
      expect(resources.requestsChangeHeight_.length).to.equal(0);
      expect(resource1.changeHeight.callCount).to.equal(1);
      expect(overflowCallbackSpy.callCount).to.equal(1);
      expect(overflowCallbackSpy.firstCall.args[0]).to.equal(false);
    });

    it('should NOT change height when in the middle of the viewport', () => {
      resource1.layoutBox_ = {top: 10, left: 0, right: 100, bottom: 100,
          height: 50};
      resources.scheduleChangeHeight_(resource1, 111, false);
      resources.mutateWork_();
      expect(resource1.changeHeight.callCount).to.equal(0);
      expect(overflowCallbackSpy.callCount).to.equal(1);
      expect(overflowCallbackSpy.firstCall.args[0]).to.equal(true);
      expect(overflowCallbackSpy.firstCall.args[1]).to.equal(111);
      expect(resource1.getPendingChangeHeight()).to.equal(111);
    });

    it('should NOT change height when below viewport, but decreases', () => {
      resource1.layoutBox_ = {top: 10, left: 0, right: 100, bottom: 210,
          height: 50};
      resources.scheduleChangeHeight_(resource1, 50, false);
      resources.mutateWork_();
      expect(resource1.changeHeight.callCount).to.equal(0);
      expect(overflowCallbackSpy.callCount).to.equal(0);
    });

    it('should defer when above the viewport and scrolling on', () => {
      resource1.layoutBox_ = {top: -1200, left: 0, right: 100, bottom: -1050,
          height: 50};
      resources.lastVelocity_ = 10;
      resources.lastScrollTime_ = new Date().getTime();
      resources.scheduleChangeHeight_(resource1, 111, false);
      resources.mutateWork_();
      expect(resources.requestsChangeHeight_.length).to.equal(1);
      expect(resource1.changeHeight.callCount).to.equal(0);
      expect(overflowCallbackSpy.callCount).to.equal(0);
    });

    it('should change height when above the vp and adjust scrolling', () => {
      viewportMock.expects('getScrollHeight').returns(2999).once();
      viewportMock.expects('getScrollTop').returns(1777).once();
      resource1.layoutBox_ = {top: -1200, left: 0, right: 100, bottom: -1050,
          height: 50};
      resources.lastVelocity_ = 0;
      clock.tick(5000);
      resources.scheduleChangeHeight_(resource1, 111, false);
      resources.mutateWork_();
      expect(resources.requestsChangeHeight_.length).to.equal(0);
      expect(resource1.changeHeight.callCount).to.equal(0);

      expect(vsyncSpy.callCount).to.be.greaterThan(1);
      const task = vsyncSpy.lastCall.args[0];
      const state = {};
      task.measure(state);
      expect(state.scrollTop).to.equal(1777);
      expect(state.scrollHeight).to.equal(2999);

      viewportMock.expects('getScrollHeight').returns(3999).once();
      viewportMock.expects('setScrollTop').withExactArgs(2777).once();
      task.mutate(state);
      expect(resource1.changeHeight.callCount).to.equal(1);
      expect(resource1.changeHeight.firstCall.args[0]).to.equal(111);
      expect(resources.relayoutTop_).to.equal(resource1.layoutBox_.top);
    });

    it('should NOT adjust scrolling if height did not increase', () => {
      viewportMock.expects('getScrollHeight').returns(2999).once();
      viewportMock.expects('getScrollTop').returns(1777).once();
      resource1.layoutBox_ = {top: -1200, left: 0, right: 100, bottom: -1050,
          height: 50};
      resources.lastVelocity_ = 0;
      clock.tick(5000);
      resources.scheduleChangeHeight_(resource1, 111, false);
      resources.mutateWork_();
      expect(resources.requestsChangeHeight_.length).to.equal(0);
      expect(resource1.changeHeight.callCount).to.equal(0);

      expect(vsyncSpy.callCount).to.be.greaterThan(1);
      const task = vsyncSpy.lastCall.args[0];
      const state = {};
      task.measure(state);
      expect(state.scrollTop).to.equal(1777);
      expect(state.scrollHeight).to.equal(2999);

      viewportMock.expects('getScrollHeight').returns(2999).once();
      viewportMock.expects('setScrollTop').never();
      task.mutate(state);
      expect(resource1.changeHeight.callCount).to.equal(1);
      expect(resource1.changeHeight.firstCall.args[0]).to.equal(111);
      expect(resources.relayoutTop_).to.equal(resource1.layoutBox_.top);
    });

    it('should reset pending change height when rescheduling', () => {
      resources.scheduleChangeHeight_(resource1, 111, false);
      resources.mutateWork_();
      expect(resource1.getPendingChangeHeight()).to.equal(111);

      resources.scheduleChangeHeight_(resource1, 112, false);
      expect(resource1.getPendingChangeHeight()).to.be.undefined;
    });

    it('should force resize after focus', () => {
      resources.scheduleChangeHeight_(resource1, 111, false);
      resources.mutateWork_();
      expect(resource1.getPendingChangeHeight()).to.equal(111);
      expect(resources.requestsChangeHeight_.length).to.equal(0);

      resources.checkPendingChangeHeight_(resource1.element);
      expect(resource1.getPendingChangeHeight()).to.be.undefined;
      expect(resources.requestsChangeHeight_.length).to.equal(1);

      resources.mutateWork_();
      expect(resources.requestsChangeHeight_.length).to.equal(0);
      expect(resource1.changeHeight.callCount).to.equal(1);
      expect(resource1.changeHeight.firstCall.args[0]).to.equal(111);
      expect(overflowCallbackSpy.callCount).to.equal(2);
      expect(overflowCallbackSpy.lastCall.args[0]).to.equal(false);
    });
  });

  describe('attemptChangeHeight rules for element wrt document', () => {

    beforeEach(() => {
      viewportMock.expects('getRect').returns(
          {top: 0, left: 0, right: 100, bottom: 10000, height: 200}).atLeast(1);
      resource1.layoutBox_ = resource1.initialLayoutBox_ =
          layoutRectLtwh(0, 10, 100, 100);
    });

    it('should NOT change height when far the bottom of the document', () => {
      viewportMock.expects('getScrollHeight').returns(10000).once();
      resources.scheduleChangeHeight_(resource1, 111, false);
      resources.mutateWork_();
      expect(resource1.changeHeight.callCount).to.equal(0);
    });

    it('should change height when close to the bottom of the document', () => {
      viewportMock.expects('getScrollHeight').returns(110).once();
      resources.scheduleChangeHeight_(resource1, 111, false);
      resources.mutateWork_();
      expect(resource1.changeHeight.callCount).to.equal(1);
    });
  });
});

describe('Resources mutateElement', () => {

  function createElement(rect) {
    return {
      tagName: 'amp-test',
      isBuilt: () => {
        return true;
      },
      isUpgraded: () => {
        return true;
      },
      getAttribute: () => {
        return null;
      },
      getBoundingClientRect: () => rect,
      applySizesAndMediaQuery: () => {},
      layoutCallback: () => Promise.resolve(),
      viewportCallback: sinon.spy(),
      prerenderAllowed: () => true,
      renderOutsideViewport: () => false,
      isRelayoutNeeded: () => true,
      contains: unused_otherElement => false,
      updateLayoutBox: () => {},
      overflowCallback: (unused_overflown, unused_requestedHeight) => {},
    };
  }

  function createResource(id, rect) {
    const resource = new Resource(id, createElement(rect), resources);
    resource.element['__AMP__RESOURCE'] = resource;
    resource.state_ = ResourceState_.READY_FOR_LAYOUT;
    resource.layoutBox_ = rect;
    resource.changeHeight = sinon.spy();
    return resource;
  }

  let sandbox;
  let viewportMock;
  let resources;
  let resource1, resource2;
  let parent1, parent2;
  let relayoutTopStub;
  let resource1RequestMeasureStub, resource2RequestMeasureStub;

  beforeEach(() => {
    sandbox = sinon.sandbox.create();
    resources = new Resources(window);
    viewportMock = sandbox.mock(resources.viewport_);
    resources.vsync_ = {
      mutate: callback => callback(),
      measure: callback => callback(),
      runPromise: task => {
        const state = {};
        if (task.measure) {
          task.measure(state);
        }
        if (task.mutate) {
          task.mutate(state);
        }
        return Promise.resolve();
      },
    };
    relayoutTopStub = sandbox.stub(resources, 'setRelayoutTop_');
    sandbox.stub(resources, 'schedulePass');

    resource1 = createResource(1, layoutRectLtwh(10, 10, 100, 100));
    resource2 = createResource(2, layoutRectLtwh(10, 1010, 100, 100));
    resources.resources_ = [resource1, resource2];

    resource1RequestMeasureStub = sandbox.stub(resource1, 'requestMeasure');
    resource2RequestMeasureStub = sandbox.stub(resource2, 'requestMeasure');

    parent1 = createElement(layoutRectLtwh(10, 10, 100, 100));
    parent2 = createElement(layoutRectLtwh(10, 1010, 100, 100));

    parent1.getElementsByClassName = className => {
      if (className == '-amp-element') {
        return [resource1.element];
      }
    };
    parent2.getElementsByClassName = className => {
      if (className == '-amp-element') {
        return [resource2.element];
      }
    };
  });

  afterEach(() => {
    viewportMock.verify();
    viewportMock = null;
    resources = null;
    sandbox.restore();
    sandbox = null;
  });

  it('should mutate from visible to invisible', () => {
    const mutateSpy = sandbox.spy();
    const promise = resources.mutateElement(parent1, () => {
      parent1.getBoundingClientRect = () => layoutRectLtwh(0, 0, 0, 0);
      mutateSpy();
    });
    return promise.then(() => {
      expect(mutateSpy.callCount).to.equal(1);
      expect(resource1RequestMeasureStub.callCount).to.equal(1);
      expect(resource2RequestMeasureStub.callCount).to.equal(0);
      expect(relayoutTopStub.callCount).to.equal(1);
      expect(relayoutTopStub.getCall(0).args[0]).to.equal(10);
    });
  });

  it('should mutate from invisible to visible', () => {
    const mutateSpy = sandbox.spy();
    parent1.getBoundingClientRect = () => layoutRectLtwh(0, 0, 0, 0);
    const promise = resources.mutateElement(parent1, () => {
      parent1.getBoundingClientRect = () => layoutRectLtwh(10, 10, 100, 100);
      mutateSpy();
    });
    return promise.then(() => {
      expect(mutateSpy.callCount).to.equal(1);
      expect(resource1RequestMeasureStub.callCount).to.equal(1);
      expect(resource2RequestMeasureStub.callCount).to.equal(0);
      expect(relayoutTopStub.callCount).to.equal(1);
      expect(relayoutTopStub.getCall(0).args[0]).to.equal(10);
    });
  });

  it('should mutate from visible to visible', () => {
    const mutateSpy = sandbox.spy();
    parent1.getBoundingClientRect = () => layoutRectLtwh(10, 10, 100, 100);
    const promise = resources.mutateElement(parent1, () => {
      parent1.getBoundingClientRect = () => layoutRectLtwh(10, 1010, 100, 100);
      mutateSpy();
    });
    return promise.then(() => {
      expect(mutateSpy.callCount).to.equal(1);
      expect(resource1RequestMeasureStub.callCount).to.equal(1);
      expect(resource2RequestMeasureStub.callCount).to.equal(0);
      expect(relayoutTopStub.callCount).to.equal(2);
      expect(relayoutTopStub.getCall(0).args[0]).to.equal(10);
      expect(relayoutTopStub.getCall(1).args[0]).to.equal(1010);
    });
  });
});

describe('Resources.TaskQueue', () => {

  let sandbox;
  let clock;
  let queue;

  beforeEach(() => {
    sandbox = sinon.sandbox.create();
    clock = sandbox.useFakeTimers();
    queue = new TaskQueue_();
  });

  afterEach(() => {
    clock = null;
    sandbox.restore();
    sandbox = null;
  });

  it('should enqueue and dequeue', () => {
    clock.tick(1000);
    expect(queue.getSize()).to.equal(0);
    expect(queue.getLastEnqueueTime()).to.equal(0);
    expect(queue.getLastDequeueTime()).to.equal(0);

    queue.enqueue({id: '1'});
    expect(queue.getTaskById('1').id).to.equal('1');
    expect(queue.getSize()).to.equal(1);
    expect(queue.getLastEnqueueTime()).to.equal(1000);
    expect(queue.getLastDequeueTime()).to.equal(0);

    expect(() => {
      queue.enqueue({id: '1'});
    }).to.throw(/Task already enqueued/);

    queue.dequeue({id: '1'});
    expect(queue.getTaskById('1')).to.equal(null);
    expect(queue.getSize()).to.equal(0);
    expect(queue.getLastEnqueueTime()).to.equal(1000);
    expect(queue.getLastDequeueTime()).to.equal(1000);
  });

  it('should perform score-based peek', () => {
    queue.enqueue({id: 'A', v: 0});
    queue.enqueue({id: 'B', v: 2});
    queue.enqueue({id: 'C', v: 1});

    const task = queue.peek(task => 10 - task.v);
    expect(task.id).to.equal('B');
  });
});

describe('Resources.Resource', () => {

  let sandbox;
  let element;
  let elementMock;
  let resources;
  let resource;
  let viewportMock;

  beforeEach(() => {
    sandbox = sinon.sandbox.create();

    element = {
      tagName: 'AMP-AD',
      isBuilt: () => false,
      isUpgraded: () => false,
      prerenderAllowed: () => false,
      renderOutsideViewport: () => true,
      build: unused_force => false,
      getBoundingClientRect: () => null,
      updateLayoutBox: () => {},
      isRelayoutNeeded: () => false,
      layoutCallback: () => {},
      changeHeight: () => {},
      documentInactiveCallback: () => false,
      viewportCallback: () => {}
    };
    elementMock = sandbox.mock(element);

    resources = new Resources(window);
    resource = new Resource(1, element, resources);
    viewportMock = sandbox.mock(resources.viewport_);
  });

  afterEach(() => {
    viewportMock.verify();
    viewportMock = null;
    resource = null;
    elementMock.verify();
    elementMock = null;
    element = null;
    sandbox.restore();
    sandbox = null;
  });

  it('should initialize correctly', () => {
    expect(resource.getId()).to.equal(1);
    expect(resource.debugid).to.equal('amp-ad#1');
    expect(resource.getPriority()).to.equal(2);
    expect(resource.getState()).to.equal(ResourceState_.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).getState()).to.equal(
        ResourceState_.NOT_LAID_OUT);
  });

  it('should not build before upgraded', () => {
    elementMock.expects('isUpgraded').returns(false).atLeast(1);
    elementMock.expects('build').never();

    // Force = false.
    expect(resource.build(false)).to.equal(false);
    expect(resource.getState()).to.equal(ResourceState_.NOT_BUILT);

    // Force = true.
    expect(resource.build(true)).to.equal(false);
    expect(resource.getState()).to.equal(ResourceState_.NOT_BUILT);
  });

  it('should build after upgraded, but before ready', () => {
    elementMock.expects('isUpgraded').returns(true).atLeast(1);
    elementMock.expects('build').withExactArgs(false).returns(false).once();
    expect(resource.build(false)).to.equal(false);
    expect(resource.getState()).to.equal(ResourceState_.NOT_BUILT);
  });

  it('should build after upgraded', () => {
    elementMock.expects('isUpgraded').returns(true).atLeast(1);
    elementMock.expects('build').withExactArgs(false).returns(true).once();
    expect(resource.build(false)).to.equal(true);
    expect(resource.getState()).to.equal(ResourceState_.NOT_LAID_OUT);
  });

  it('should force-build after upgraded', () => {
    elementMock.expects('isUpgraded').returns(true).atLeast(1);
    elementMock.expects('build').withExactArgs(true).returns(true).once();
    expect(resource.build(true)).to.equal(true);
    expect(resource.getState()).to.equal(ResourceState_.NOT_LAID_OUT);
  });

  it('should blacklist on build failure', () => {
    elementMock.expects('isUpgraded').returns(true).atLeast(1);
    elementMock.expects('build').withExactArgs(true)
        .throws('Failed').once();
    expect(resource.build(true)).to.equal(false);
    expect(resource.blacklisted_).to.equal(true);
    expect(resource.getState()).to.equal(ResourceState_.NOT_BUILT);

    // Second attempt would not even try to build.
    expect(resource.build(true)).to.equal(false);
  });

  it('should mark as ready for layout if already measured', () => {
    elementMock.expects('isUpgraded').returns(true).atLeast(1);
    elementMock.expects('build').returns(true).once();
    const stub = sandbox.stub(resource, 'hasBeenMeasured').returns(true);
    resource.build(false);
    expect(stub.calledOnce).to.be.true;
    expect(resource.getState()).to.equal(ResourceState_.READY_FOR_LAYOUT);
  });

  it('should mark as not laid out if not yet measured', () => {
    elementMock.expects('isUpgraded').returns(true).atLeast(1);
    elementMock.expects('build').returns(true).once();
    const stub = sandbox.stub(resource, 'hasBeenMeasured').returns(false);
    resource.build(false);
    expect(stub.calledOnce).to.be.true;
    expect(resource.getState()).to.equal(ResourceState_.NOT_LAID_OUT);
  });

  it('should allow to measure when not upgraded', () => {
    elementMock.expects('isUpgraded').returns(false).atLeast(1);
    resource.resources_ = {
      viewport_: {
        getLayoutRect() {
          return layoutRectLtwh(0, 100, 300, 100);
        }
      }
    };
    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_.NOT_BUILT);
  });

  it('should measure and update state', () => {
    elementMock.expects('isUpgraded').returns(true).atLeast(1);
    elementMock.expects('build').returns(true).once();
    expect(resource.build(true)).to.equal(true);

    elementMock.expects('getBoundingClientRect')
        .returns({left: 11, top: 12, width: 111, height: 222})
        .once();
    elementMock.expects('updateLayoutBox')
        .withExactArgs(sinon.match(data => {
          return data.width == 111 && data.height == 222;
        }))
        .once();
    resource.measure();
    expect(resource.getState()).to.equal(ResourceState_.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);
  });

  it('should update initial box only on first measure', () => {
    elementMock.expects('isUpgraded').returns(true).atLeast(1);
    elementMock.expects('build').returns(true).once();
    expect(resource.build(true)).to.equal(true);

    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 noop request measure when not built', () => {
    expect(resource.isMeasureRequested()).to.be.false;
    elementMock.expects('getBoundingClientRect').never();
    resource.requestMeasure();
    expect(resource.isMeasureRequested()).to.be.false;
  });

  it('should request measure when built', () => {
    expect(resource.isMeasureRequested()).to.be.false;
    elementMock.expects('getBoundingClientRect').never();
    resource.state_ = ResourceState_.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_.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_.READY_FOR_LAYOUT);
  });

  it('should not relayout if has box has not changed', () => {
    resource.state_ = ResourceState_.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_.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_.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_.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_.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_.READY_FOR_LAYOUT);
    expect(resource.getLayoutBox().width).to.equal(111 + 10);
  });

  it('should ignore startLayout if already completed or failed or going',
        () => {
          elementMock.expects('layoutCallback').never();

          resource.state_ = ResourceState_.LAYOUT_COMPLETE;
          resource.startLayout(true);

          resource.state_ = ResourceState_.LAYOUT_FAILED;
          resource.startLayout(true);

          resource.state_ = ResourceState_.READY_FOR_LAYOUT;
          resource.layoutPromise_ = {};
          resource.startLayout(true);
        });

  it('should fail startLayout if not built', () => {
    elementMock.expects('layoutCallback').never();

    resource.state_ = ResourceState_.NOT_BUILT;
    expect(() => {
      resource.startLayout(true);
    }).to.throw(/Not ready to start layout/);
  });

  it('should ignore startLayout if not visible', () => {
    elementMock.expects('isUpgraded').returns(true).atLeast(1);
    elementMock.expects('getBoundingClientRect')
        .returns({left: 1, top: 1, width: 1, height: 1}).once();

    elementMock.expects('layoutCallback').never();

    resource.state_ = ResourceState_.READY_FOR_LAYOUT;
    resource.layoutBox_ = {left: 11, top: 12, width: 0, height: 0};
    resource.startLayout(true);
  });

  it('should force startLayout for first layout', () => {
    elementMock.expects('isUpgraded').returns(true).atLeast(1);
    elementMock.expects('getBoundingClientRect')
        .returns({left: 1, top: 1, width: 1, height: 1}).once();

    elementMock.expects('layoutCallback').returns(Promise.resolve()).once();

    resource.state_ = ResourceState_.READY_FOR_LAYOUT;
    resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 10};
    resource.startLayout(true);
    expect(resource.getState()).to.equal(ResourceState_.LAYOUT_SCHEDULED);
  });

  it('should ignore startLayout for re-layout when not opt-in', () => {
    elementMock.expects('isUpgraded').returns(true).atLeast(1);
    elementMock.expects('getBoundingClientRect')
        .returns({left: 1, top: 1, width: 1, height: 1}).once();

    elementMock.expects('layoutCallback').never();

    resource.state_ = ResourceState_.READY_FOR_LAYOUT;
    resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 10};
    resource.layoutCount_ = 1;
    elementMock.expects('isRelayoutNeeded').returns(false).atLeast(1);
    resource.startLayout(true);
    expect(resource.getState()).to.equal(ResourceState_.LAYOUT_COMPLETE);
  });

  it('should force startLayout for re-layout when opt-in', () => {
    elementMock.expects('isUpgraded').returns(true).atLeast(1);
    elementMock.expects('getBoundingClientRect')
        .returns({left: 1, top: 1, width: 1, height: 1}).once();

    elementMock.expects('layoutCallback').returns(Promise.resolve()).once();

    resource.state_ = ResourceState_.READY_FOR_LAYOUT;
    resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 10};
    resource.layoutCount_ = 1;
    elementMock.expects('isRelayoutNeeded').returns(true).atLeast(1);
    resource.startLayout(true);
    expect(resource.getState()).to.equal(ResourceState_.LAYOUT_SCHEDULED);
  });

  it('should ignore startLayout when document is hidden' +
        ' and prerender not allowed', () => {
    elementMock.expects('isUpgraded').returns(true).atLeast(0);
    elementMock.expects('getBoundingClientRect')
        .returns({left: 1, top: 1, width: 1, height: 1}).atLeast(0);
    elementMock.expects('prerenderAllowed').returns(false).atLeast(1);

    elementMock.expects('layoutCallback').never();

    resource.state_ = ResourceState_.READY_FOR_LAYOUT;
    resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 10};
    resource.layoutCount_ = 0;
    resource.startLayout(false);
    expect(resource.getState()).to.equal(ResourceState_.READY_FOR_LAYOUT);
  });

  it('should proceed startLayout when document is hidden' +
        ' and prerender is allowed', () => {
    elementMock.expects('isUpgraded').returns(true).atLeast(0);
    elementMock.expects('getBoundingClientRect')
        .returns({left: 1, top: 1, width: 1, height: 1}).atLeast(0);
    elementMock.expects('prerenderAllowed').returns(true).atLeast(1);

    elementMock.expects('layoutCallback').returns(Promise.resolve()).once();

    resource.state_ = ResourceState_.READY_FOR_LAYOUT;
    resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 10};
    resource.layoutCount_ = 0;
    resource.startLayout(false);
    expect(resource.getState()).to.equal(ResourceState_.LAYOUT_SCHEDULED);
  });

  it('should complete startLayout', () => {
    elementMock.expects('isUpgraded').returns(true).atLeast(1);
    elementMock.expects('getBoundingClientRect')
        .returns({left: 1, top: 1, width: 1, height: 1}).once();

    elementMock.expects('layoutCallback').returns(Promise.resolve()).once();

    resource.state_ = ResourceState_.READY_FOR_LAYOUT;
    resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 10};
    const loaded = resource.loaded();
    const promise = resource.startLayout(true);
    expect(resource.layoutPromise_).to.not.equal(null);
    expect(resource.getState()).to.equal(ResourceState_.LAYOUT_SCHEDULED);

    return promise.then(() => {
      expect(resource.getState()).to.equal(ResourceState_.LAYOUT_COMPLETE);
      expect(resource.layoutPromise_).to.equal(null);
      return loaded;  // Just making sure this doesn't time out.
    });
  });

  it('should fail startLayout', () => {
    elementMock.expects('isUpgraded').returns(true).atLeast(1);
    elementMock.expects('getBoundingClientRect')
        .returns({left: 1, top: 1, width: 1, height: 1}).once();

    elementMock.expects('layoutCallback').returns(Promise.reject()).once();

    resource.state_ = ResourceState_.READY_FOR_LAYOUT;
    resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 10};
    const promise = resource.startLayout(true);
    expect(resource.layoutPromise_).to.not.equal(null);
    expect(resource.getState()).to.equal(ResourceState_.LAYOUT_SCHEDULED);

    return promise.then(() => {
      fail('should not be here');
    }, () => {
      expect(resource.getState()).to.equal(ResourceState_.LAYOUT_FAILED);
      expect(resource.layoutPromise_).to.equal(null);
    });
  });

  it('should change height and update state', () => {
    resource.state_ = ResourceState_.READY_FOR_LAYOUT;
    elementMock.expects('changeHeight').withExactArgs(111).once();
    resource.changeHeight(111);
    expect(resource.getState()).to.equal(ResourceState_.NOT_LAID_OUT);
  });

  it('should change height but not state', () => {
    resource.state_ = ResourceState_.NOT_BUILT;
    elementMock.expects('changeHeight').withExactArgs(111).once();
    resource.changeHeight(111);
    expect(resource.getState()).to.equal(ResourceState_.NOT_BUILT);
  });

  describe('setInViewport', () => {
    it('should call viewportCallback when not built', () => {
      resource.state_ = ResourceState_.NOT_BUILT;
      elementMock.expects('viewportCallback').withExactArgs(true).once();
      resource.setInViewport(true);
      expect(resource.isInViewport()).to.equal(true);
    });

    it('should call viewportCallback when built', () => {
      resource.state_ = ResourceState_.LAYOUT_COMPLETE;
      elementMock.expects('viewportCallback').withExactArgs(true).once();
      resource.setInViewport(true);
      expect(resource.isInViewport()).to.equal(true);
    });

    it('should call viewportCallback only once', () => {
      resource.state_ = ResourceState_.LAYOUT_COMPLETE;
      elementMock.expects('viewportCallback').withExactArgs(true).once();
      resource.setInViewport(true);
      resource.setInViewport(true);
      resource.setInViewport(true);
    });
  });

  describe('documentInactiveCallback', () => {
    it('should NOT call documentInactiveCallback on unbuilt element', () => {
      resource.state_ = ResourceState_.NOT_BUILT;
      elementMock.expects('viewportCallback').never();
      elementMock.expects('documentInactiveCallback').never();
      resource.documentBecameInactive();
      expect(resource.getState()).to.equal(ResourceState_.NOT_BUILT);
    });

    it('should call documentInactiveCallback on built element and update state',
        () => {
          resource.state_ = ResourceState_.LAYOUT_COMPLETE;
          elementMock.expects('documentInactiveCallback').returns(true).once();
          resource.documentBecameInactive();
          expect(resource.getState()).to.equal(ResourceState_.NOT_LAID_OUT);
        });

    it('should call documentInactiveCallback on built element' +
        ' but NOT update state', () => {
      resource.state_ = ResourceState_.LAYOUT_COMPLETE;
      elementMock.expects('documentInactiveCallback').returns(false).once();
      resource.documentBecameInactive();
      expect(resource.getState()).to.equal(ResourceState_.LAYOUT_COMPLETE);
    });

    it('should NOT call viewportCallback when resource not in viewport', () => {
      resource.state_ = ResourceState_.LAYOUT_COMPLETE;
      resource.isInViewport_ = false;
      elementMock.expects('viewportCallback').never();
      resource.documentBecameInactive();
    });

    it('should call viewportCallback when resource in viewport', () => {
      resource.state_ = ResourceState_.LAYOUT_COMPLETE;
      resource.isInViewport_ = true;
      elementMock.expects('viewportCallback').withExactArgs(false).once();
      resource.documentBecameInactive();
    });

    it('should delegate unload to documentInactiveCallback', () => {
      resource.state_ = ResourceState_.LAYOUT_COMPLETE;
      elementMock.expects('documentInactiveCallback').returns(false).once();
      resource.unload();
      expect(resource.getState()).to.equal(ResourceState_.LAYOUT_COMPLETE);
    });
  });

  describe('getResourcesInViewport', () => {
    let resource1;
    let resource2;

    beforeEach(() => {
      resource1 = {
        hasOwner: () => false,
        isDisplayed: () => true,
        prerenderAllowed: () => true,
        overlaps: () => true
      };
      resource2 = {
        hasOwner: () => false,
        isDisplayed: () => true,
        prerenderAllowed: () => true,
        overlaps: () => false
      };
      resources.resources_ = [resource1, resource2];
    });

    it('should return a subset of resources that are currently ' +
       'in the viewport', () => {
      expect(resources.get().length).to.equal(2);
      expect(resources.getResourcesInViewport().length).to.equal(1);
    });

    it('should not return resources that are not allowed to prerender if ' +
       'in prerender mode', () => {
      resource1.prerenderAllowed = () => false;
      expect(resources.get().length).to.equal(2);
      expect(resources.getResourcesInViewport(false).length).to.equal(1);
      expect(resources.getResourcesInViewport(true).length).to.equal(0);
    });
  });
});


Please find down below a summary of all of the alcohol and drug residential rehab centres in Bronkhorstspruit. Bronkhorstspruit supplies private, residential rehabilitation for dependency in most of our cutting-edge treatment centres. Our organizations assist our clients and they are administered around the clock inside of a secure and caring environment.


text:
Opioid Trial Delayed Again Due to COVID-19 Concerns
This article was originally published by WSAZ
HUNTINGTON, W.Va. (WSAZ) – A lawsuit accusing pill distributers of being responsible for the opioid epidemic is being delayed again.
The trial was scheduled for the end of August before being pushed back to start in October. Now it will not get underway until next year.
A federal judge delayed the trial from Oct. 19 to Jan. 4. Earlier this month, defense attorneys requested the delay, saying they were afraid the trial could create a COVID-19 super-spreader event.
They said it would be irresponsible to hold a long trial with hundreds of witnesses from all over the country with the pandemic going on.
Paul Farrell Jr. is one of the attorneys representing the plaintiffs. He says he feels like they could have held the trial this month safely.
"We feel like Charlie Brown trying to kick the football," Farrell said. "Obviously we respect the COVID epidemic, but we should fear the opioid epidemic. There are still people suffering and dying every day in West Virginia."
He says the past six weeks, their trial team has quarantined in a bubble in South Carolina.
"We had 30 lawyers, paralegals, secretaries, and we removed them all from their homes and built basically our own NBA bubble in Charleston, South Carolina, to try to keep everybody safe," Farrell said.
He says having another delay now is disappointing.
"The frustrating thing is we've conducted over 150 depositions throughout the COVID summer without a single exposure," he said. "We've been waiting three and a half years for our day in court. We'll get there. If the Lord wants us to be patient, we'll be patient a little longer, but eventually, there will be a reckoning."

redpajama_set_name: RedPajamaCommonCrawl


Hi there, I'm adding in accounts infor from the past so I can start using the system. I have lots of transactions that are the same. Can I add multiple 'bills' at once?
Sure. That all depends on how you want your accounts to look ie do you want detail or not. We put all our software invoices (bullets revenue) in as one monthly invoice.
95Re-employing a previous employee that has worked somewhere else this year110Just wondering is this FREE13hey Guys, I just setup a new account as I have gone from a Sole Trader to Ltd company. I am only a new company, but VAT returns and P30 returns are over due from the start of the year. I can I just mark these as paid or how so I cancel them?16Hello, Could you kindly let me know if I can change my VAT periods on BHQ to 4 monthly, as that is the VAT period on the ROS system?33Hello, I have registered for corporation tax only, and not paye and not vat. (I won't be running payroll with this company). Is there a way to tell Bullet this information as it is telling me to file P30s ? Many thanks.


April 2 – massive – English Now!
"What a massive achievement!" is one of the useful phrases for this week. What sort of situation might you use this in?
For me, last month, my niece got married and the ceremony and dinner reception were lovely. She and her husband planned it all, and got it all paid for in cash by themselves, which I think these days in Australia, is a massive achievement. Most new couples pay for their wedding with credit cards! Or their parents pay. I think the fact that they saved for many months, in order to pay cash, really shows their commitment.
And, her older brother, my nephew, was at the wedding. He and his partner had flown in from Singapore, where they live and work. My nephew succeeded in setting up a chain of restaurants in Melbourne, and then in Singapore. After selling these restaurants, he found a job working for an engineering company in their top management section. Wow! That too is a massive achievement.
I'm so proud of them both! They are so young, but have both achieved massive success already!
Has anyone around you – or have you – had any massive achievements in life?


text:
Four tonnes of fish removed from NSW river
Dominica SandaAAP
Thu, 31 January 2019 3:30PM
Camera IconNSW Department of Primary Industries estimates close to one million fish died at Menindee.
Four tonnes of dead fish have been scooped out of the Darling River as the clean-up from the latest mass kill in NSW's far west winds up.
The NSW Department of Primary Industries estimates close to one million fish died over the weekend at Menindee - the latest in a series of mass deaths in the area this summer.
All the fish had been cleared from the river by Thursday afternoon with contractors to review the clean-up operation on Friday, the DPI said.
Officials are also investigating the death of thousands of fish on the Murrumbidgee River in Redbank Weir about 100km west of Hay.
Up to 10,000 fish are believed to have died including Murray cod, golden perch, silver perch and carp.
The same weather conditions which contributed to the Menindee deaths - a period of hot weather followed by a drop in temperatures - are likely to be the cause of the Murrumbidgee event, the DPI said.
The DPI warned hot and dry conditions pose ongoing threats to native fish over the remainder of summer.
Locals, irrigators and politicians have been arguing over who's to blame for the catastrophe with residents pointing the finger at water mismanagement.
A royal commission into the management of the Murray-Darling Basin Plan released its report on Thursday and was scathing of NSW Regional Water Minister Niall Blair's comments about the fish kills.
It said Mr Blair's public statements were "grossly irresponsible".
The report found river allotments were driven by politics and accused the basin authority board of maladministration over its disregard for science.
The NSW government said it will fully consider the report's findings before releasing an interim response on Friday.
German conservatives elect Merz as leader
Former Malaysian PM Mahathir in hospital


You can get this small script on SourceForge.
By doing this small script, my goal was to have a NAS where I can retrieve old versions of my files. To avoid to use too much space on the disks, the solution relies on the Hardlink concept.
In short, the same files are stored only once, but are referenced several time by different path's names.
More specifically, each backup create a folder formated by a specific name with a reference to a date. All target files by the backup system are referenced inside this folder. In other words, by looking in the correct folder's date, you will have all files as it was on that exact day. You can travel in the time. This is nothing less that the concept of the "time machine".
For those backups, I'm using a specific features of rsync relying on the HardLink.
All the rest of the script is just "salt and pepper" in order to have something you can execute from your crontab or from the console (interactive). Like removing the oldest backups; the backups older than the pre-defined value.
Size: The folder's size change compared to the previous backup. For example, if you add or remove one file in the targeted folder, this parameter will trigger a backup. But if you change a file which has an impact on the file's size, this will also trigger a backup.
Full: The backup will be triggered by a folder's size change or by a file's change (mtime) since the last backup. With this option, you can trigger a backup even if the change you performed does not impact the file's size.
The second option is mainly useful for folders having text files (code, html, ... ). In fact where changes could not impact the "sizes".
On every run, the script will check for triggers. If yes, a new folder will be created and we will store there all current files. By default the script is foreseen to runs 1x per day.
`.time_machine'. This is the config file for this folder.
The config file must have 1 mandatory parameter: backup_type.
The next parameter is optional: historical_retention.
perform a backup, the script will not create a backup folder.
After each execution, the script could create one backup folder next to "current".
drwxr-xr-x   8 vi    wheel   512 Jul 19 01:35 .
drwxr-xr-x   5 vi    wheel   512 Jul 19 17:32 ..
drwxr-xr-x   28 vi    vi  1024 Jul 21 01:43 .
drwxrwx---   12 vi    vi   512 Feb 26 17:53 ..
Because the current "view" of a targeted folder is in "current", you can "hide" this time machine principle to your end user by doing soft links to it.
At home all my kids have, in their home's dir of their own machines, a folder called "nas" linked to their own folder on the NAS. This folder is backuped by the Time Machine. This setup assure me that their files are backuped every day automatically.
Moreover, the backup directories are mounted; via NFS, in readonly in their home directory as "time machine". By doing this way, they can recover their old version easily without breaking it (by removing or renaming backuped files).
All in all, after 2 years (all backups have reached their maximum occurrences of retention), the extra space taken by the hardlinks is less than 9%. (i've compared the size of the "current" folders and the size of the whole backups with the du command). But, this ratio can be very different if I had removed several big files.
Thanks to this backup system, I've saved some of my files after of wrong manipulations.


text:
Los alemanes de Crimea (alemán: Krimdeutsche) fueron colonizadores alemanes étnicos que fueron invitados a colonizar Crimea como parte del Ostsiedlung o colonización de Oriente.

Historia
Desde 1783 en adelante, hubo un asentamiento sistemático de rusos, ucranianos y alemanes a la península de Crimea (en lo que entonces era el Kanato de Crimea) con el fin de debilitar a la población tártara de Crimea.

Los primeros asentamientos planificados de alemanes en Crimea se fundaron durante 1805-10 con el apoyo del zar Alejandro I. Los primeros asentamientos fueron:
 Friedental - en el distrito de Simferópol; formado en 1806 por luteranos
 Heilbrunn - en el distrito de Feodosia; formado en 1809 por luteranos
 Kronental - en el distrito de Simferópol; formado en 1810 por luteranos y católicos
 Neusatz - en el distrito de Simferópol; formado en 1806 por luteranos
 Rosental - en el distrito de Simferópol; formado en 1806 por católicos
 Staryj Krim - en el distrito de Feodosia; formado en 1805 por luteranos y católicos
 Sudak - en el distrito de Feodosia; formed en 1805 por luteranos
 Zuerichtal - en el distrito de Feodosia; formed en 1805 por suizos luteranos
Todas estas primeras colonias estaban ubicadas en los montes de Crimea y eran en su mayoría vitivinicultores de Suabia. Sin embargo, con el tiempo solamente Sudak produjo vino de calidad y los otros asentamientos volvieron pronto a la agricultura. La segunda generación no tenían suficientes tierras y pronto los jóvenes empezaron a comprar tierras de la aristocracia rusa y crearon nuevas colonias.

Después los menonitas empezaron a migrar de Ucrania a Crimea.

Persecución soviética
El 18 de octubre de 1921 se creó la llamada República Autónoma Socialista Soviética de Crimea como parte de la República Socialista Federativa Soviética de Rusia (es decir, parte de Rusia). En lugar de lo que hoy es el raión de Krasnogvardeyskiy se crearon dos distritos nacionales para los alemanes: Biyuk-Onlar y Telman. Bajo el régimen soviético muchos Volksdeutsche fueron perseguidos por bandas de campesinos rusos como terratenientes kuláks o enemigos burgueses. En 1939, dos años antes de su deportación a Asia Central, alrededor de 60.000 de los 1,1 millones de habitantes de Crimea eran alemanes y tenían su propio raión administrativo en la República de Crimea.

Invasión nazi, deportación y exilio
A fines de 1941, tras la invasión nazi de las regiones occidentales de la URSS (Campaña de Crimea), las autoridades soviéticas deportaron brutalmente a los nativos alemanes de Crimea hacia el este, a Siberia y Asia central basándose en acusaciones completamente falsas de que eran espías del Tercer Reich. En consecuencia, muchos murieron en el camino, aunque después no se les culparon de crímenes nazis en la región.

No está claro si algún alemán permaneció durante la ocupación nazi pues la política alemana era evacuar a los sobrevivientes soviéticos de origen alemán a asentamientos en Polonia. El Generalkommissar nazi Alfred Frauenfeld jugó con la idea de reasentar a los alemanes étnicos (Volksdeutsche) de Tirol del Sur en Crimea después de la guerra, y varias ciudades fueron rebautizadas con nombres alemanes (Simferópol se convirtió en Gotenburg (Gotemburgo) y Sebastopol se convirtió en Theodorichhafen, por ejemplo).

Los pocos exiliados sobrevivientes se dispersaron por todo el mundo. En Canadá, Reynold Rapp, un agricultor e inmigrante luterano de Crimea, se convirtió en parlamentario por el Partido Conservador Progresivo. Como fuerte defensor de la herencia británica y de la libertad en su país adoptivo, Rap se opuso a la sustitución de la insignia roja canadiense con la nueva bandera con hoja de arce como insignia en 1964. Él dijo a los periodistas: "Puedo ser el único hombre en esta Cámara que ha vivido bajo el martillo y la hoz. Una bandera no es sólo un colorín: representa mucho más que eso".

Actualidad
Recién después de la Perestroika, se les permitió a los alemanes de Crimea a retornar a la península. Hoy, Crimea es vista meramente como un camino para entrar en Alemania por las personas jóvenes de ancestro alemán. La reunificación alemana trajo un renacimiento de la cultura crimeo-germana y, desde 1994, tienen una pequeña representación en el Parlamento de Crimea.

Tras la anexión de Crimea a Rusia en 2014, de los dos millones de habitantes de Crimea, alrededor de 3.000 poseen ascendencia alemana.

Véase también
 Alemanes de Rusia
 Alemanes del mar Negro
 Spätaussiedler
 Godos de Crimea

Referencias

Diáspora alemana
Etnias de Ucrania

redpajama_set_name: RedPajamaWikipedia


text:
Feb. 5, 2017 / 2:51 AM
Zach LaVine (knee) lost to Minnesota Timberwolves for season
Washington Wizards guard John Wall (2) scores and is fouled by Minnesota Timberwolves guard Zach LaVine (8). UPI/Mark Goldman | License Photo
Minnesota Timberwolves guard Zach LaVine tore the anterior cruciate ligament in his left knee and will need season-ending surgery, the team announced Saturday.
LaVine was injured with 7:20 remaining in the third quarter of Friday's 116-108 road loss to the Detroit Pistons.
He drove through the lane, attempted a layup while colliding with Detroit center Andre Drummond near the basket. LaVine made the shot but fell hard and clutch his left knee.
LaVine actually played for nearly another six minutes and the first 37 seconds of the fourth quarter before leaving the game.
LaVine won the NBA dunk contest in his first two seasons. This year, the former UCLA standout is averaging career-highs of 18.9 points, 3.4 assists and 37.2 minutes per game.
Dubai Desert Classic: Sergio Garcia leads by 3 shots Byeong Hun An shoots 65, leads by one in Phoenix Open No. 13 Oregon Ducks use 3-point barrage to rout No. 5 Arizona Wildcats
NBA // 2 days ago
Pelicans' Zion Williamson expected to debut Jan. 22 against Spurs
Jan. 15 (UPI) -- New Orleans Pelicans rookie Zion Williamson -- the No. 1 overall pick in the 2019 NBA Draft -- is expected to make his debut against the San Antonio Spurs Jan. 22 in New Orleans.
Spurs' DeMar DeRozan throws down vicious dunk over Raptors
Jan. 13 (UPI) -- DeMar DeRozan coasted through defenders and threw down a vicious slam dunk during the San Antonio Spurs' win against the Toronto Raptors.
Brooklyn Nets' Kyrie Irving returns to starting lineup vs. Atlanta Hawks
Jan. 12 (UPI) -- Brooklyn Nets star point guard Kyrie Irving is in the starting lineup for Sunday's game against the Atlanta Hawks.
NBA // 1 week ago
Philadelphia 76ers' Joel Embiid to undergo surgery for torn ligament in left hand
Jan. 9 (UPI) -- Philadelphia 76ers star center Joel Embiid will be out indefinitely after suffering a torn ligament in his left hand.
Lakers' Anthony Davis takes hard fall, expected to miss time
Jan. 8 (UPI) -- All-Star Anthony Davis fell to the floor while attempting a block and did not return to the game during a Los Angeles Lakers win against the New York Knicks.
Detroit Pistons' Blake Griffin out indefinitely following knee surgery
Jan. 7 (UPI) -- Detroit Pistons star forward Blake Griffin is out indefinitely after he underwent a successful arthroscopic procedure on his injured left knee.
Hawks star Trae Young shows off deceptive dribbles against Nuggets
Jan. 7 (UPI) -- Atlanta Hawks star Trae Young displayed some impressive dribble moves while fooling several defenders and scoring as part of an electric sequence during a loss to the Denver Nuggets.
Los Angeles Lakers listening to trade offers for Kyle Kuzma
Jan. 6 (UPI) -- General manager Rob Pelinka and the Los Angeles Lakers are listening to trade offers for third-year forward Kyle Kuzma.
Clippers' Paul George swishes circus shot over backboard vs. Knicks
Jan. 6 (UPI) -- Los Angeles Clippers star Paul George managed to make a one-handed circus shot after throwing the ball over the backboard while behind the hoop during a win over the New York Knicks.
Los Angeles Lakers fan sinks half-court shot for $100K
Jan. 6 (UPI) -- A Los Angeles Lakers fan swished a shot from half court to win $100,000 between quarters during the team's 106-99 win against the Detroit Pistons at Staples Center in Los Angeles.


text:
Svijet

Natjecanja

Svjetska natjecanja
 20. travnja – 12. rujna – VII. Olimpijske igre – Antwerpen 1920.

Kontinentska natjecanja

Europska natjecanja

Osnivanja
 FK Sutjeska Nikšić, crnogorski nogometni klub
 SC Heerenveen, nizozemski nogometni klub
 NK Koper, slovenski nogometni klub
 CA Osasuna, španjolski nogometni klub
 Cagliari Calcio, talijanski nogometni klub
 Empoli F.C., talijanski nogometni klub

Rekordi

Nagrade

Ostali događaji

Rođenja

Smrti

Hrvatska i u Hrvata

Natjecanja

Timski športovi

Pojedinačni športovi

Osnivanja
 Splitski nogometni podsavez

Rekordi
 17. svibnja – Franjo Škrinjar, hrvatski atletičar († 1989.)

Nagrade

Ostali događaji

Rođenja

Smrti

Izvori

Vanjske poveznice


The Lufthansa and SWISS multi-continent airfares are now on sale for travel to the end of 2019. If you're planning to travel to 3 continents during the 2019, now's the time to snap up an Earlybird Sale fare. Economy 3 Continent Global Fares start from $1673 and Business class from $6062. These fares provide you with a choice of over 100 European destinations. Contact our RTW Airfare Specialists to construct your perfect trip.


text:
Turning Grief for a Hidden Past Into a Healing Space
Memorial to Enslaved Laborers at the University of Virginia. Photograph by Sanjay Suchak for The New York Times.
In a recent New York Times piece, Holland Cotter explains how the citizens of Charlottesville Virginia have been grappling with the city's statues, and the histories the monuments honor, for years. Cotter discusses the impact of the range of monuments in this historic city-- paying special attention to the 'Memorial to Enslaved Laborers.' Erected in 2016, this monument honors, and in some cases identifies, the enslaved peoples who contributed to the University of Virginia's legacy. The memorial has a special added touch from New York-based artist Eto Otitigbe. Otitigbe, who exhibited with AGBS in 2016, engraved an image of the eyes of Isabella Gibbons, an enslaved woman at the University of Virginia, into the monument.
"For generations, and with few exceptions, Americans have tended to ignore their old political monuments, tune them out as neutral features of the civic landscape, neither innocent, nor guilty; just there. The Charlottesville incident changed that. Suddenly, we saw certain images for what they were: ideological weapons, dirty bombs of history. The police killing of George Floyd in May opened our eyes still wider." -Holland Cotter


text:
Israel's issues on religion and state – return to status quo or a new era?
POLITICAL AFFAIRS: Since we don't yet know what the government will do, what are the most dramatic clauses in the agreements, taken at face value?
By ELIAV BREUER
UKRAINIAN IMMIGRANTS are greeted by then-absorption minister Pnina Tamano-Shata (third right) upon their arrival at Ben-Gurion Airport in February.
(photo credit: TOMER NEUBERG/FLASH90)
The coalition agreements that were signed this week include countless clauses related to religion in general, and religion and state in particular.
They range from new funding for an array of programs and institutions to promote Jewish identity; clauses about the status of the Western Wall and the Temple Mount; to cancellation of the previous government's kashrut and "kosher cellphone" reform. And the list goes on.
Clauses in coalition agreements that don't have defined numbers and dates are seldom translated into action, and even detailed plans often do not pan out. But since we don't yet know what the government will do, what are the most dramatic clauses in the agreements, taken at face value?
Shlomit Ravitsky Tur-Paz, director of the Shared Society Center at the Israel Democracy Institute and one of the country's leading experts on matters of religion and state, laid out her answer to this question in an interview with The Jerusalem Post.
According to Ravitsky Tur-Paz, the incoming coalition's most dramatic implication is on the question of "Who is a Jew" – regarding conversion and the Law of Return.
UTJ LEADER Yitzchak Goldknopf is setting the tone of the new government and religion and state in Israel. Will Benjamin Netanyahu give him everything? (credit: YONATAN SINDEL/FLASH90)
The agreements call on the state to go back to the pre-2015 situation. In 2015, the High Court ruled that the state had to recognize independent conversion courts, whether Orthodox or any other denomination. The ruling came after years in which the court nearly begged the state to decide on the issue – but the legislature was unable or unwilling to do so, Ravitsky said.
The agreements, however, do not affect the decisions made in the 1980s to recognize Reform and Conservative conversions done abroad, in order to be careful not to anger Diaspora Jews, Ravitsky pointed out.
Since 2016, the official Chief Rabbinate-mandated conversion courts approved approximately 3,000 conversions per year, while the private courts oversaw another 3,000-plus conversions in total. This means the private conversion courts held nearly 600 a year, amounting to more than 15% of all conversions.
All data presented in this article are taken from a biannual report on religion and state authored by Ravitsky Tur-Paz, Ariel Finkelstein and Ayala Goldberg from the Israel Democracy Institute, which has not yet been published.
In Ravitsky Tur-Paz's short historical brief of conversion in Israel, she explained that a High Court ruling in 1970 recognized the children of Jewish fathers and non-Jewish mothers as Jews.
The legislature responded in 1970 by enacting a law that struck a balance. On the one hand, we recognize for civil purposes anyone who has a Jewish grandparent as a kind of "Jewish descendant," but on the other hand, according to traditional Jewish law, a Jew is only someone who is born to Jewish mother. This was meant as a trade-off.
The famous Prof. Ruth Gavizon-Rabbi Yaakov Medan Covenant also proposed a trade-off, albeit a slightly different one.
On one hand, it would shrink the Grandchild Clause so that it would demand from those who arrive via the Law of Return to fulfill a "preparation period," and perhaps study Hebrew and swear allegiance to the state, but on the other hand, the state would recognize Reform and Conservative conversions carried out in Israel. This too, would strike a balance between the citizenship aspect and the religious aspect.
The incoming coalition's philosophy is to shrink the definition on both ends. On the one hand, it wants to cancel the Grandchild Clause, and at the same time cancel non-Rabbinate conversions. This, instead of being a trade-off, would narrow the ability both to move to Israel and to become Jewish within the country, Ravitsky Tur-Paz said.
Many of the people who come to Israel but are not Jewish according to law still feel Jewish – more than 90%, according to data Ravitsky Tur-Paz quoted from the One Million Lobby, which acts on behalf of immigrants from the former USSR. Canceling the Grandchild Clause would be a slap in the face.
According to the IDI's data, if in 1990 93% of people who made aliyah from the former USSR were Jewish, in 2015 that number fell to 42% and in 2020 to just 23.8%, which amounted to approximately 3,000 Jewish olim. The Rabbinate's conversions conduct approximately 3,000 a year, and therefore if the downward trend in Jewish olim continues and private conversion courts are banned, the number of non-Jews making aliyah will eclipse the number of those converting, Ravitsky Tur-Paz explained.
Another motivation to change the law is to prevent taking advantage of aliyah benefits. The number of people who came and then left after a year or two after doubled in 2017, when the law was changed so that people received a passport and benefits immediately upon coming to Israel, instead of waiting a year with a temporary passport. This incentivizes people to take advantage of the option to receive citizenship – but the concept of the "interim passport" could be brought back without canceling the Law of Return, Ravitsky Tur-Paz said.
Another interesting aspect is agreement on the Left to diminish the Law of Return on the grounds that it discriminated against Arabs, who don't have this same right.
However, there was widespread support from both the Left and the Right to leave the law as it is, Ravitsky Tur-Paz explained. On the Left, this comes from sociological grounds – a Jew can also be someone who identifies as such even if he is not Jewish according to Halacha. On the Right, there is also an incentive to keep things as they are for demographic reasons – people who come using the Grandchild Clause are not Arab and therefore tilt the demographic towards the Jewish majority.
The issue is therefore not as simple as a Right-Left divide, she said.
Public opinion is yet another factor. Some 70% of Jewish Israelis believe a person born to a non-Jewish mother is not Jewish, as opposed to 26% who do – and even approximately 50% of secular Jews in Israel do not believe that a person born to a non-Jewish mother is Jewish, versus 44% who do.
Forty-four percent of Israeli Jews do not believe that people who underwent non-Orthodox conversions are Jewish, versus 40% who do; while 69% of Israel's Jews believe that people who underwent conversion in the IDF are indeed Jewish, versus 18% who do not believe so.
The second dramatic issue, according to Ravitsky Tur-Paz is surprising – the agreement for the state to fund regular burial plots for those who wish so.
The issue requires some explanation.
Israel currently has approximately 12,400 dunams set aside for burial. Regular burial enables 250 graves per dunam, leaving approximately three million spots. With Israel's population approaching 10 million, this means that babies born today will have nowhere to be buried – unless the country sets aside more precious land for this purpose.
Add to this the fact that in Jewish law, burial spots are eternal and are not dug up after a certain period, and what you get is a coalition policy that chooses the dead over the living, the expert explained.
The state developed a number of methods in order to cope with this, under the title of "layered burial."
These include familial graves, where families are buried in a deeper grave one over the other (600 per dunam); multi-tiered graves, which enables 750 graves per dunam; or "Sanhedrin burial," which enables 1,300 graves per dunam.
Another innovative solution is called "bone-gathering," whereby bones are collected a year after burial and are then put into a casing and reburied in a smaller plot. This could allow 3,500 plots per dunam, leaving room for over 150 million spots for current and future citizens. This was done during the post-Second Temple period, Ravitsky Tur-Paz explained, and is accepted by some religious-Zionist rabbis, but not by the Rabbinate, she explained.
The layered burial methods are offered for free, and until now regular burial was offered at a relatively high price. Canceling this will completely erase the incentive to agree to layered burial, and this could have dramatic implications for the future of Israel's land reserves, Ravitsky Tur-Paz explained.
Although the Rabbinate approved the layered burial, the public's attitude towards the idea is complicated. The ultra-Orthodox public nearly unanimously disapproves of layered burial, since it is still a change to usual burial practice; the secular public is far more open to it. Interestingly, people who define themselves as religious Zionist are more willing to use layered burial than people who define themselves as traditional, Ravitsky Tur-Paz said.
Religion and state will remain a contentious issue throughout the Knesset's tenure – and issues of "who is a Jew" and burial are just the tip of the iceberg.
Tags Knesset freedom of religion in israel israeli politics coalition


text:
Hurricane Jose Strengthens to 'Extremely dangerous' Category Four
by: ERIC LEVENSON, CNN
Posted: Sep 8, 2017 / 09:56 PM CDT / Updated: Sep 9, 2017 / 06:30 PM CDT
Hurricane Jose strengthened to an "extremely dangerous" Category 4 storm Friday, with maximum sustained winds near 150 mph, according to the National Hurricane Center.
The storm was about 335 miles east-southeast of the Northern Leeward Islands at 5 p.m. ET Friday, moving west-northwest at 17 mph, the National Hurricane Center said.
A hurricane warning was put into effect Thursday for Barbuda and Anguilla, Saint Martin and Saint Barthelemy. A hurricane watch remained in effect for Antigua.
The eye of Irma passed over Barbuda, a tiny Caribbean island of about 1,800 residents, on Wednesday, destroying telecommunication systems and cell towers. The storm damaged about 95% of the buildings on the island, Prime Minister Gaston Browne said.
This is the first time on record that the Atlantic has had two hurricanes with 150-plus mph winds at the same time, Colorado State University meteorologist Philip Klotzbach said.
In addition, Hurricane Katia in the Gulf of Mexico has maximum sustained winds of 100 mph and is expected to make landfall in Mexico early Saturday, the National Hurricane Center said.
Fluctuations in Hurricane Jose's intensity are possible for the next day or so, the National Hurricane Center said, and the storm is expected to gradually weaken after that.
More FOX24 Stories
by Crystal Martinez / Jan 28, 2020
FAYETTEVILLE, Ark. (KFTA) - "She was one of the happiest people I've ever met," Clint Goodwin said.
Clint Goodwin and Brittany Butler have been married for one year. "[She] always tried to help somebody else," Goodwin said. " [She] caused a lot of problems for herself but never tried to hurt anyone else."
2020 Census public awareness meeting
by Megan Wilson / Jan 28, 2020
GENTRY, Ark. (KFTA) — A meeting in Gentry focused on outreach for the 2020 census.
Benton County Judge Barry Moehiring's office partnered with the Gentry Library for the meeting.
Alcohol allowed in park for special events
SPRINGDALE, Ark. (KFTA) — Alcohol is now allowed to be served at a Springdale park during special events.
The Springdale City Council voted in favor of allowing alcohol at Walter Turnbow Park in downtown Springdale.
FOX24 Video
You Ask, We Investigate: Polystyrene Trays in Schools
KNWA News Today
Bentonville sees return on tourism investment
Signature Bank hosts Soup-er Bowl of Caring event


text:
Q: How to type a function for renaming object keys in Typescript I have a function that takes a string transformation function and a an object and applies the transformation to all of the keys, returning the resulting object.
In particular I'm using Ramda
Here is my function (untyped for clarity).
const renameBy = R.curry(
  (fn, obj) => R.pipe(
      R.toPairs,
      R.map([k, v] => [fn(k), v]),
      R.fromPairs
    )(obj)
);

I'm well aware of the issues currying can cause, so I'm really only looking for solutions addressing the core function (and optionally the second unary function, renameBy(fn)) and resolving a nicer return type than the current R.Dictionary, which basically never matches anything.
I've been looking here, dealing with renaming specific keys of an object and here, dealing with recursive, deep nested-object renaming but neither seems to quite be what I'm looking for.
Example of usage
const input = {
     foo_bar: "foobar",
     bar: "bar",
     bar_foo: "barfoo"
}

const camelize = (paramName) =>
  paramName.replace(
    /_(.?)/g,
    $1 => $1.substr(1).toUpperCase()
  );

renameBy(camelize, input)

const expectedOutput = {
     fooBar: "foobar",
     bar: "bar",
     barFoo: "barfoo"
}

It's possible I could use something like
function renameBy<
     F extends (str: string) => string,
     N extends string[],
     M extends Record<string, any>
   >(fn: F, obj: M): { [P in N]: M[keyof M] }
      {...}

But I can't quite get it to work.

A: It has been a while since this question was discussed. Recently, I faced a similar task. Anyway, here is a possible solution with TS 4.1+
// helper types
type BaseT = Record<string, unknown> // object constraint
type BaseM = Record<string, string> // mapping constraint
type KVs<T extends BaseM> = keyof T | T[keyof T] // keys and values

type Remap<T extends BaseT, M extends BaseM> =
  & {[K in keyof M & keyof T as M[K]]: T[K]} // new keys
  & {[K in keyof T as Exclude<K, KVs<M>>]: T[K]} // unchanged keys

type Result = Remap<{a: 1; b: '2'}, {a: 'c'}>
// type Result = {b: '2'} & {c: 1}

redpajama_set_name: RedPajamaStackExchange


Select only the environment and health and safety information you need! Get direct access to specific topics covering the legislation and news relevant to you.
Receive only the specific news and information topics you need.
View articles organized by relevant topic areas such as health and safety or Hazardous Materials.
Find what you need with our easy-to-use search engine.
Research and select only relevant legislation topics. Our comprehensive database includes Canadas 1,800 federal, provincial and territorial environment and safety laws, published in full text.
Stay responsive to recent developments, federal and provincial/territorial government announcements, and news from industry and organizations.


Knowing the kinds of people who live in our community and the problems they face can be an incredible benefit when it comes to programming for ministry and outreach. In a church plant where I'm involved, we ran a demographic program search for a three mile geographic radius surrounding our meeting location. We were provided more than 25 pages of data on this area to help us understand our community. We were able to review more than 100 different categories to help us better understand our neighborhood. We even received maps that identified where various parts of the population resided. I especially liked the first few pages of the report that summarized the data in an easy to understand Q and A format. The image at right is what it looked like.
There was a lot more information than this, but notice just a couple of realities from this snapshot. Q and A 4 revealed that our area is high in racial diversity. This could present a problem for our church in reaching our immediate geography since we do not have as much racial diversity in our core group. We began to think about what kind of ways we as a core team could become more diverse, and if that were not possible, perhaps we could focus on a specific group that our outreach teams could target for mission?
Notice that Q & A 5 gave us insight into the various age groups of our community. Had we thought about programs or events that might connect with each segment? Even Q & A 1 was surprising to us since we never would have guessed that so many people lived in such a small geographic area. The harvest field was dense, but did we understand that reaching even one percent of the population would mean a church attendance of 480? Sometimes data like this can really open our eyes to what is around us and the opportunity God has granted us to make a difference in people's lives. It can help us to see things differently, or perhaps see for the very first time the harvest filed around us!
In the information age, there is available to us a volume of data that can help us think more strategically about our outreach and church programming.
As I mentioned, the demographic program we used can provide more than 100 different kinds of data and can provide geographic areas of study from as small as a city block to as large as a state county. The best part is that the information is free to any Grace Brethren church that wants to use it!


text:
Spanish duo for Tarxien Rainbows
9 January 2015 18 December 2018 by Antoine Busuttil
After releasing four of their foreign players, Tarxien Rainbows secured the services of two Spanish players Eduardo Vallecillo Casado and Jorge Yepes Roda.
A product of Real Valladolid, twenty-three year old Edu Vallecillo played for Valladolid's second team, Recreativo Huelva's second team, Palencia, Atlético Astorga and CD Aurora in Bolivia. Twenty-five year old Yepes, who is a midfielder, is a product of Xerex.
The other foreigners on the books of Tarxien are Carlos Menendez Hevia and Alessandro Lopes Pereira. Pipico, Clebinho, Branquinho and Jorge Santos were released by the club last month.
← Ten Premier League players suspended
Malta retains 147th place in FIFA Ranking →
25 minutes 8 seconds ago
Photos from MFA Women's Football's post
Alexis Espitia wins Assikura Award for December
Sara Saliba claims Assikura Women's Youth League Player Award for December
Archives Select Month February 2023 January 2023 December 2022 November 2022 October 2022 September 2022 August 2022 July 2022 June 2022 May 2022 April 2022 March 2022 February 2022 January 2022 December 2021 November 2021 October 2021 September 2021 August 2021 July 2021 June 2021 May 2021 April 2021 March 2021 February 2021 January 2021 December 2020 November 2020 October 2020 September 2020 August 2020 July 2020 June 2020 May 2020 April 2020 March 2020 February 2020 January 2020 December 2019 November 2019 October 2019 September 2019 August 2019 July 2019 June 2019 May 2019 April 2019 March 2019 February 2019 January 2019 December 2018 November 2018 October 2018 September 2018 August 2018 July 2018 June 2018 May 2018 April 2018 March 2018 February 2018 January 2018 December 2017 November 2017 October 2017 September 2017 August 2017 July 2017 June 2017 May 2017 April 2017 March 2017 February 2017 January 2017 December 2016 November 2016 October 2016 September 2016 August 2016 July 2016 June 2016 May 2016 April 2016 March 2016 February 2016 January 2016 December 2015 November 2015 October 2015 September 2015 August 2015 July 2015 June 2015 May 2015 April 2015 March 2015 February 2015 January 2015 December 2014 November 2014 October 2014 September 2014 August 2014 July 2014 June 2014 May 2014 April 2014 March 2014 February 2014 January 2014 December 2013 November 2013 October 2013 September 2013 August 2013 July 2013 June 2013 May 2013 April 2013 March 2013 February 2013 January 2013 December 2012 November 2012 October 2012 September 2012 August 2012 July 2012 June 2012 May 2012 April 2012 March 2012 February 2012 January 2012 December 2011 November 2011 October 2011 September 2011 August 2011 July 2011 June 2011 May 2011 April 2011 March 2011 February 2011 January 2011 December 2010 November 2010 October 2010 September 2010 August 2010 July 2010 June 2010 May 2010 April 2010 March 2010 February 2010 January 2010 December 2009 November 2009 October 2009 September 2009 August 2009


I am beginning to notice that a some people in Haiti ONLY like to get paid, they want money to take care of their business but they don't want to put in the the hours.
KREYOL: Anpil moun HAITI, yo sèlman vle TOUCHE, men yo pa vle TRAVAY... Parese pase yo nanpwen... Vrai ou Faux??? Kisa ou panse de sa???
A business friend of mine told me he is having problems with employees at his food distribution warehouse, every time he hires someone to do the heavy lifting that person shows motivation for the first few weeks and then he begins to slack off.
"It's almost like, as soon as you give them a smile, they think they are you friend," she said, "and they begin to sit around not wanting to do the work."
Another local businessman is having a different problem. Next to his business is a "baz domino" (a spot where people are playing dominoes)... "Every time I am need my employee, I have to KICK and SCREAM and YELL," he says, "Where the hell is he? Watching the domino game instead of of doing what I pay him for."
"Some people in Haiti ONLY want to get PAID," he said, "they don't want to work."