text:
New Chippewa SWCD Contact Information
October 26, 2021 By Desirae Jarett
The Chippewa SWCD contact information has changed. Please take note of the following:
Chippewa SWCD Main Line: 320-269-2696
Desirae Jarett, Office & Financial Coordinator
Ext: 2692
Tom Sletta, District Technician
Tom Warner, District Manager
Zach Bothun, Technical Manager
Filed Under: Slider
May 1, 2020 By Desirae Jarett
The Chippewa County Courthouse is currently open to the public. Currently, the USDA Service Center is open on a limited basis. Please call before coming.
Chippewa SWCD board meetings will return to being held beginning May 3, 2021 and will be held in the Commissioner's Room of the Chippewa County Courthouse.
To reach NRCS or SWCD staff please call 320-269-2139 ext. 3. We apologize for any inconvenience this may cause.
March 16, 2020 By Desirae Jarett
Virginia Homme named 2019 Chippewa SWCD Conservationist of the Year
Written by: Tom Cherveny, WCTribune
Tom Warner, SWCD District Manager, presents a Redlin print to Virginia Homme, 2019 Chippewa SWCD Conservationist of the Year, in recognition of her strong commitment to conservation practices.
The Chippewa County SWCD Conservationist of the Year may have put the years of making hay, tending to cattle and raising crops behind her, but caring for the land remains her passion.
Virginia Homme lives on a very diverse, 17-acre farm site that is part of a quarter section in Granite Falls Township. The property has belonged to the Homme family since the early 1940's. Farming has been a tradition for the families of Virginia Homme and her late husband, Paul, for generations.
A ditch runs through the fields and farm site on its path to the nearby Minnesota River. To protect against erosion and improve water quality, Virginia has planted buffer plots on slopes next to the ditch.
She had the first planted as a native pollinator plot in 2018. She said it has proven to be very successful. It blooms with a succession of colorful, nectar-bearing flowers through the length of the growing season.
It's pleasing to the eye, and provides habitat for wildlife too. This past summer, a wild turkey chose to nest in the middle of the plot. Now, wild turkeys are part of her neighborhood.
Homme had the second plot planted with a mix of native grasses and forbes to benefit pollinators in the fall of 2019. Like the first plot, it is coming into its own and doing well, she said. The native perennials hold the soil with their roots and provide year around cover on the ditch slopes. The plants protect against wind and water erosion that would otherwise wash the rich glacial soils into the ditch and Minnesota River.
It takes commitment. Virginia and her son, Erik, hand weeded box elder seedlings and other, undesired plants. She enlisted a neighbor's help too. He used his old Alice Chalmers tractor with a belly mower to perform mid-season mowing.
Virginia Homme made this site her home in 2008. She said that she realized right away that a portion of the area where the ditch flows through the farm site looked like a natural place for a wetland or small pond.
She presented the idea to the Chippewa County Soil and Water Conservation District staff this past summer. They enlisted the help of an engineer, who agreed with her assessment and also saw the benefits it could provide. A wetland at this site could help slow the flow of water from a large Conservation Reserve Program wetland that is located upstream and across the road from this quarter section.
Weather and funding permitting, Homme said she is hopeful the wetland project will happen this year.
She's also made her mark on the local landscape by making her home a model for energy independence and reducing our carbon footprint. Her home is heated and cooled with an energy-efficient geo-thermal system. In late August of 2010, she sought to further reduce her carbon footprint by turning to solar and wind power to produce the electricity to power her system and light her home.
Green Energy Products had a system online for her by late fall. She now has a 3.7 KW wind turbine and a 6.44 KW solar panel at work. She received a 30 percent tax credit for the costs of installing the systems. She points out that the costs of installing either are much lower today.
Solar has proven to be the better value for small scale production, according to Homme. It costs less to install than wind and doesn't require the maintenance. The solar panels have saved 146,000 pounds of CO2 from being added to our atmosphere. The wind turbine saves about 20 percent more of the CO2 total.
Virginia Homme sought to reduce her carbon footprint by turning to solar and wind power to produce the electricity to power her energy-efficient farm and home. (Photo courtesy of CURE)
Her recent accomplishments are in keeping with a life-long commitment to good stewardship.
Virginia and her husband Paul graduated from the St. Paul campus of the University of Minnesota and married as the Korean war flared and a draft was in place. Paul joined the Air Force and remained in his military career for 21 years.
Yet during most of those years, the family would use their vacation and leave time to return to Granite Falls to help Paul's father with the haying and his cow calf operation.
When Paul retired in 1976, the couple began farming on the family land. They built an energy efficient house on the bluff of the Minnesota River across from the Upper Sioux Agency State Park in Hawk Creek Township, Renville County.
(Paul died suddenly at age 74 in 2004, just nine months shy of what would have been the couple's 50th wedding anniversary.)
Some of the fields they farmed were river bottom land prone to spring flooding. By the early 80's, there were even summer floods that ruined their crops. One year, there was even a fall flood.
The couple went to a lot of meetings in hopes there would be steps taken to slow the upstream water and reduce flooding, but to no avail.
They enrolled a portion of the flood prone land in the Reinvest in Minnesota or RIM wetland program. The remainder is protected from erosion as pastures and used for rotational grazing for grass fed beef.
The steep river bluff on this land is restored as well, thanks to our conservationist. In 2017 she was able to work with the short-lived Working Lands program. It provided cost-sharing assistance to remove invasive cedars from the bluff and allow native seeds to once again emerge. The hillside was historically a dry prairie, and these special grasses and forbes once again make this a special landscape.
Thousands of visitors to the Upper Sioux Agency State Park enjoy the view of the restored bluffland, the floodplain wetlands, and the waterfowl and other wildlife they attract. Most of them are unaware of the individual who has made this possible. With her recognition as this year's Chippewa SWCD Conservationist of the Year, perhaps more people will now realize how much Virginia Homme has done to protect our lands.
December 14, 2018 By Desirae Jarett
On December 11, 2018 Jerry and Ruth Ann Lee were recognized as the Chippewa SWCD Conservationist of the Year the the MASWCD Annual Convention in Bloomington, MN.
Following their return, Jerry and Ruth Ann were congratulated and presented at the Chippewa SWCD board meeting on December 13th.
Please look for our article in our annual report featuring Jerry and Ruth Ann.
Jerry and Ruth Ann at the Annual Convention
Ruth Ann & Jerry at their stream bank restoration project along the Chippewa River.
The Lees with District Manager, Tom Warner at their recognition.
April 5, 2018 By Desirae Jarett
VanRavenswaay named Chippewa County Conservationist of the Year
The Chippewa SWCD presents Carl Van Ravenswaay as Conservationist of the Year for 2017.
Carl, wife Wendy, and their children live near Montevideo. Carl farms a corn-bean rotation with some black beans using conventional tillage on 2,450 acres that he shares in a joint venture with Halvorson Management.
Carl is active in using conservation practices including water and sediment control basins,terraces, waterways, tree plantings, filter strips, CRP,buffers and food plots. Carl also custom feeds hogs,completes annual soil testing, and utilizes covercrops, knife injected manure spreading, and integrated pest management.
In 2017, he worked with the Soil and Water Conservation District, the Natural Resource Conservation Service (NRCS), and the landowners to install a series of conservation practices. Carl utilized the SWCD's State Cost Share program and NRCS' Environmental Quality Incentive Program (EQIP) to install two narrow grass back terraces totaling 2,035 feet and two water and sediment control basins.
Through the SWCD's Disaster Recovery Assistance Program (DRAP) a 300 foot water and sediment control basin was also installed. Carl worked with NRCS and through CRP he helped to install 2,337 feet of grass waterway adjacent to the Chippewa River. He also seeded down a grass filter strip. In 2013, 2014, and 2015 Carl completed 1,303 acres of both Nutrient and Pest Management through EQIP and the National Water Quality Initiative (NWQI).
Carl played a key role in the processes of installing these projects. He was very patient and understanding throughout the process. He coordinated contract paperwork, estimates and bids between the landowners, the contractors, and the SWCD and NRCS office. He took it upon himself to do some of the legwork and helped with some of the steps to complete them.
The SWCD and NRCS appreciates the work that Carl puts into not only his land but his rented land as well.
Board Chair Scott Roelofs presents Carl and Wendy Van Ravenswaay at the December SWCD Board Meeting
Andresen named Chippewa County Conservationist of the Year
Mike Andresen, of Montevideo, has been named the
2016 Chippewa SWCD Conservationist of the Year.
The Chippewa Soil and Water Conservation District
nominated Andresen, and he received the honor at
the Minnesota Association of Soil & Water Conservation
District's (MASWCD) annual state convention recently held in
Bloomington. He was one of 64 other conservationists who were
honored from other counties in the state.
Andresen farms a corn and soybean rotation with conservation
tillage on 125 acres. He has previously been an Minnesota Pollution
Control Agency water quality volunteer. In 2015 he worked with
NRCS and the SWCD to install two water and sediment control
basins and one grade stabilization structure. He also installed
a native grass wildlife habitat planting and seeded a critical area
planting in 2015. From 2013 to 2015 he maintained
nutrient/pest management on 205 acres.
Andresen has 4,700 feet of established field windbreaks on
one of his farms, as well as a farmstead shelterbelt. He has a
total of 74.86 acres enrolled in the Conservation Reserve Program
(CRP) and has 28 acres of pasture land. Mike is also a conservation
contractor and did all of his own work, installing the erosion
control practices on his own.
Andresen is also an avid sportsman. Recently, Mike and fellow
members of the Tri-County Sportsman Club built and opened
Mills Creek Gun Range on five acres of Mike's land just north of
Montevideo. He was recognized locally at the SWCD board
meeting in early December with a gift from the
Chippewa SWCD, which was a rustic, custom made sign that was
made out of black walnut, deer antlers, and a painting of a whitetail
deer scene by local craftsman and artist Doug Pederson.
Mike Andresen, of Montevideo, has established conservation practices
on much of his land in Chippewa County, including 28 acres of
pasture land and 74.86 acres in the Conservation Reserve Program.
Scott Roelofs (right), district supervisor of the Chippewa SWCD, presented
Mike Andresen with a gift that Andresen will display in his hunting
shack.
Buffer Cost Share and Extensions
August 22, 2017 By Desirae Jarett
Buffer extensions are available on protected waters only. Protected waters are to be seeded by November 1, 2017. If you may need an extension please submit a written request to the Chippewa SWCD Board of Supervisors. Please include a location/map, the reason for needing the extension, and your plan for becoming compliant. Extensions give landowners until July 1, 2018 to be in compliance.
Extension requests will be reviewed by the board and landowners will be notified of approval and/or denial. All extension requests should be either mailed to or dropped off at the Chippewa SWCD office on or before September 29, 2017 to allow the board time to approve or deny them prior to the November 1st deadline.
The Chippewa SWCD office address is: 629 N. 11th Street, Suite 7, Montevideo, MN 56265.
BUFFER COST SHARE
Update: As of March 5, 2018, all allocated cost share has been encumbered.
Buffer cost share is now available for both protected waters and county ditches. Buffer cost share in Chippewa County will be $300 per acre. Cost share is to be used for seed, seeding, and maintenance of the buffer. Cost share is available only one time per acre. The acres allowed for cost share are only those needed to bring the landowner into compliance with the buffer law. If you are interested please contact the office to be put on the list as there are a limited amount of funds available. Cost share will be paid out after the buffer has been established and approved by the Chippewa SWCD. Seed tags will be required to be turned in to the SWCD. The office can be reached by calling 320-269-2139 extension 3.
Seeding Services
June 24, 2013 By Desirae Jarett Leave a Comment
Fall 2021 seeding is FULL. To order seed or to be added to the spring 2022 seeding schedule please contact Zach.
SWCD Offers Seed Sales & Seeding
The Chippewa County Soil & Water Conservation District (SWCD) is now offering Landowners the opportunity to buy native grass seed and other cool and warm season mixes. Landowners also have the option to have seed planted by the SWCD.
Chippewa SWCD can plant warm and cool season grass plantings as well as certain wildlife food plot mixtures ranging from small seed mixes on up to large seed mixes.
Drill service includes 1 operator, tractor and drill. The price for seeding is charged by the amount of acres planted plus mobilization fees:
$100 flat mobilization fee within Chippewa County (add $1.00 per mile for out of County Projects)
$25/acre planting fee ($100 minimum planting fee for 4 acres and less)
If a landowner chooses to purchase seed through the SWCD, the prices will vary on seed type and quantity.
Seeding Dates:
Warm season grasses require soil temperatures of at least 50 degrees before they will germinate. This is 8 to 10 degrees warmer than the requirement for cool season grasses. Therefore, the optimal seeding time for planting warm season grasses, either alone or in combination with cool season grasses, usually occurs between mid-spring and early summer when moisture and temperature are most favorable. May 15th to June 30th is the typical window for planting warm season grasses in this area.
Note: Late April through June is a busy time for the district. The tree program is usually in full swing at the same time CRP and other grass plantings need to be planted. Contacting the district early on to get on the list for planting is crucial. Unfortunately there will be a limit to how many acres can be planted each spring by the district.
Seedbed Condition:
Note: It is the landowners' responsibility to prepare a decent seedbed before planting.
A firm seedbed is very important when seeding native grasses. The districts Truax seed drill needs a firm seedbed in order to turn the double disc openers properly which is crucial for seed planting depth. A firm seedbed also helps conserve moisture and ensure good seed to soil contact which is critical for adequate germination and an overall successful planting. IMPORTANT: Recently tilled ground should be packed with a coil or roller packer, or something equivalent prior to planting. The seedbed is considered firm enough when an adult's footprint penetrates no more than 1/4 to 1/2 inch deep. For example: untilled bean stubble works great for seeding with the Truax drill.
To order seed or get on the seeding list, please contact Zach or Tom at:
320-269-2139 ext.105 – Zach
320-269-2139 ext.115 – Tom
Tree Ordering
If you are interested in ordering trees for 2022, please contact Tom Warner. Availability is based on the nursery stock.
Below is the link to the tree and shrub order form. If there is a variety not listed that you are interested in, please contact us. Please note, all bareroot trees are 18-24″ unless otherwise specified. Larger sized bareroot prices depend on nursery pricing and availability as well as quantity order. Contact staff for special orders. Special orders are sold by the bundle.
Tree & Shrub Order Form 2021
2016 Poster Contest Theme
"We All Need Trees"
2016 5th Grade Poster Contest Winners


text:
New Jersey Lawyers Blog
Published By McLaughlin & Nardi, LLC
Third Circuit Court of Appeals Defines Standard for Finding "Willful" Violations of the Fair Labor Standards Act
by Maurice W. McLaughlin
Willful Violations Under The Fair Labor Standards Act
The Federal Fair Labor Standards Act establishes rates of minimum wage and overtime pay which employers must pay to their employees. Employees successfully suing their employers for violations of these requirements can recover their lost wages, and their employers will be required to pay their attorneys fees and litigation costs. The Fair Labor Standards Act provides that willful violations of these requirements will result in double damages – ie., the employer will be required pay the employee twice the amount of wages or overtime it did not pay. A willful violation also extends the statute of limitations for suing from two years to three.
Willfulness: The Question Facing the Third Circuit
In the case of Linda Stone v. Troy Construction, LLC, the Untied States Court of Appeals for the Third Circuit faced the question of how to determine "willfulness" in a case where a company wrongfully paid "per diem" rather than increasing the employees' salaries, thus decreasing the rate used to calculate their overtime pay. While the case arose out of Pennsylvania, the Third Circuit's rulings are binding on federal cases in New Jersey, and are persuasive authority in state cases under New Jersey's Wage and Hour Law.
Factual Background
Troy Construction, LLC, operates nationally, building and maintaining oil and gas pipelines and compressor stations. Much of its work was in Pennsylvania. The employees it used there were a mix of workers from other areas who had to travel great distances, and many who lived close to the worksites. Troy paid the non-local employees per diem to cover their travel expenses. However, Troy also paid local employees per diem even though their travel expenses were insignificant, far below the per diem amount paid. In fact, the per diem payments often constituted a major portion of their pay, sometimes more than half. Troy's executives testified that the purpose of all the per diem was to reimburse employees for their out of pocket travel expenses.
In 2014, Troy ceased paying local employees per diem on the advice of their accountants, and started treating these payment as taxable income. However, despite treating these payments as taxable income, rather than reimbursement of expenses, Troy did not include these payments in the calculation of overtime rates. The effect can be readily seen; for an employee making $10 per hour whose "per diem" was half their pay, the per diem would total another pro rata $10 dollars per hour, for a total of $20 per hour in pay of all types. Thus, if the "per diem was not included in the calculation, the employee's overtime rate would be $15 per hour –$10 per hour at "time and a half." If the "per diem" was included in the calculation, the employee's overtime rate would be $30 per hour –$20 per hour at "time and a half."
Stone's Suit in Federal District Court
More than two years after her last day of work, but less than three years, Stone filed a collective action under the Fair Labor Standards Act suing Troy on behalf of herself and other similarly situated employees of Stone for the unpaid overtime compensation the employees would have received had Troy included the "per diem" in their overtime rates. The trial court dismissed the suit on summary judgment, claiming that there was insufficient evidence of a willful violation, and thus Stone had filed her lawsuit too late. Stone appealed.
The Third Circuit's Decision Defining "Willful" Violations Under the Fair Labor Standards Act
The Third Circuit reversed. It found the trial court applied the wrong standard to determine willfulness – which will determine the eligibility for double damages as well as the proper statute of limitations – and thus significant questions remained its the case to be determined by a jury.
According to the Third Circuit, the trial court erred in placing the burden on the employee to show that the employer's actions were "purposeful." Rather, the standard for finding a willful violation of the Fair Labor Standards Act is that "the employer showed reckless disregard for the matter of whether its conduct was prohibited by the FLSA." Here, the change in accounting showed that Troy knew its payment method of paying local employees per diem was incorrect, and that it had to be paid as taxable income. Troy therefore had all the facts it needed to know that it was required to include the "per diem" payments in calculation of the employees' overtime rate. Egregiousness and willfulness are evidence of a "willful" violation, but are not required. Reckless disregard of those requirements will suffice.
As the court explained, "In sum, willfulness under the Fair Labor Standards Act is established where the employer either knew or showed reckless disregard for the matter of whether its conduct was prohibited by the [Fair Labor Standards Act]."
Our New Jersey employment law attorneys represent employers and employees in all aspects of employment law. Call us at (973) 890-0004, or fill out the contact form on this page to schedule a consultation with one of our employment lawyers. We can help.
Posted in: Construction Law and Labor and Employment Law
Tagged: "New Jersey Employment Lawyers" and New Jersey Wage and Hour Attorneys
Updated: September 26, 2019 12:50 pm
Labor and Employment (149)
Estate Administration and Probate (17)
Construction Law (14)
Solid Waste Law (10)
Appellate Division Addresses Jurisdiction for Appeals of Tenure Charges Under TEACHNJ Act January 15, 2020
Appellate Division Issues First Published Opinion on the New Jersey Pregnant Workers Fairness Act January 14, 2020
McLaughlin & Nardi, LLC Attorneys Successfully Reversed a Decision to Remove an Applicant from a Civil Service List for Appointment as a Jersey City Police Officer January 13, 2020
37 Vreeland Ave
Totowa, NJ 07512
Copyright © 2015 – 2020, McLaughlin & Nardi, LLC

redpajama_set_name: RedPajamaCommonCrawl


text:
Q: How to send model data as parameter in MVC3? I am trying to pass model data as a parameter in my MVC 3 application. i write the follwoing code to achive the functionality
<script type="text/javascript">
     $(function () {
         var modelData = <%= Model.ToJson() %>;
     });

 </script>

also write a JsonExtensions.cs class to extend the method. here I am getting "expression expected" error before the <% sign and getting following error when i run the application
"Microsoft JScript runtime error: Could not complete the operation due to error 80020101."
I am not sure what i am missing here

redpajama_set_name: RedPajamaStackExchange


text:
import {AmpDocSingle, installDocService} from '../../src/service/ampdoc-impl';
import {
  installViewportServiceForDoc,
  Viewport,
  ViewportBindingDef,
  ViewportBindingIosEmbedWrapper_,
  ViewportBindingNatural_,
  ViewportBindingNaturalIosEmbed_,
  parseViewportMeta,
  stringifyViewportMeta,
  updateViewportMetaString,
} from '../../src/service/viewport-impl';
import {getMode} from '../../src/mode';
import {installPlatformService} from '../../src/service/platform-impl';
import {installTimerService} from '../../src/service/timer-impl';
import {installViewerServiceForDoc} from '../../src/service/viewer-impl';
import {installVsyncService} from '../../src/service/vsync-impl';
import {loadPromise} from '../../src/event-helper';
import {setParentWindow} from '../../src/service';
import {toggleExperiment} from '../../src/experiments';
import {vsyncFor} from '../../src/vsync';
import * as sinon from 'sinon';

describes.fakeWin('Viewport', {}, env => {
  let clock;
  let viewport;
  let binding;
  let viewer;
  let viewerMock;
  let windowApi;
  let ampdoc;
  let viewerViewportHandler;
  let viewerScrollDocHandler;
  let updatedPaddingTop;
  let viewportSize;
  let vsyncTasks;

  beforeEach(() => {
    clock = sandbox.useFakeTimers();

    windowApi = env.win;
    windowApi.requestAnimationFrame = fn => window.setTimeout(fn, 16);

    viewerViewportHandler = undefined;
    viewerScrollDocHandler = undefined;
    viewer = {
      isEmbedded: () => false,
      getParam: param => {
        if (param == 'paddingTop') {
          return 19;
        }
        return undefined;
      },
      onMessage: (eventType, handler) => {
        if (eventType == 'viewport') {
          viewerViewportHandler = handler;
        } else if (eventType == 'scroll') {
          viewerScrollDocHandler = handler;
        }
      },
      sendMessage: sandbox.spy(),
      isVisible: () => true,
      onVisibilityChanged: () => {},
    };
    viewerMock = sandbox.mock(viewer);
    const ampdocService = installDocService(windowApi, /* isSingleDoc */ true);
    ampdoc = ampdocService.getAmpDoc();
    installTimerService(windowApi);
    installPlatformService(windowApi);
    installViewerServiceForDoc(ampdoc);
    binding = new ViewportBindingDef();
    viewportSize = {width: 111, height: 222};
    binding.getSize = () => {
      return {width: viewportSize.width, height: viewportSize.height};
    };
    binding.getScrollTop = () => 17;
    binding.getScrollLeft = () => 0;
    binding.connect = sandbox.spy();
    binding.disconnect = sandbox.spy();
    updatedPaddingTop = undefined;
    binding.updatePaddingTop = paddingTop => updatedPaddingTop = paddingTop;
    viewport = new Viewport(ampdoc, binding, viewer);
    viewport.fixedLayer_ = {
      update: () => {
        return {then: callback => callback()};
      },
      updatePaddingTop: () => {},
    };
    viewport.getSize();

    // Use window since Animation by default will use window.
    const vsync = vsyncFor(window);
    vsyncTasks = [];
    sandbox.stub(vsync, 'canAnimate').returns(true);
    sandbox.stub(vsync, 'createAnimTask', (unusedContextNode, task) => {
      return () => {
        vsyncTasks.push(task);
      };
    });
  });

  afterEach(() => {
    expect(vsyncTasks.length).to.equal(0);
    viewerMock.verify();
  });

  function runVsync() {
    const tasks = vsyncTasks.slice(0);
    vsyncTasks = [];
    tasks.forEach(function(task) {
      const state = {};
      if (task.measure) {
        task.measure(state);
      }
      task.mutate(state);
    });
  }

  describe('top-level classes', () => {
    let root;

    beforeEach(() => {
      root = windowApi.document.documentElement;
      root.className = '';
    });

    it('should set singledoc class', () => {
      new Viewport(ampdoc, binding, viewer);
      expect(root).to.have.class('i-amphtml-singledoc');
    });

    it('should not set singledoc class', () => {
      sandbox.stub(ampdoc, 'isSingleDoc', () => false);
      new Viewport(ampdoc, binding, viewer);
      expect(root).to.not.have.class('i-amphtml-singledoc');
    });

    it('should set standalone class', () => {
      new Viewport(ampdoc, binding, viewer);
      expect(root).to.have.class('i-amphtml-standalone');
      expect(root).to.not.have.class('i-amphtml-embedded');
    });

    it('should set embedded class', () => {
      sandbox.stub(viewer, 'isEmbedded', () => true);
      new Viewport(ampdoc, binding, viewer);
      expect(root).to.have.class('i-amphtml-embedded');
      expect(root).to.not.have.class('i-amphtml-standalone');
    });

    it('should not set iframed class', () => {
      new Viewport(ampdoc, binding, viewer);
      expect(root).to.not.have.class('i-amphtml-iframed');
    });

    it('should set iframed class', () => {
      ampdoc.win.parent = {};
      new Viewport(ampdoc, binding, viewer);
      expect(root).to.have.class('i-amphtml-iframed');
    });
  });

  it('should connect binding right away when visible', () => {
    expect(binding.connect).to.be.calledOnce;
    expect(binding.disconnect).to.not.be.called;
  });

  it('should disconnect binding on dispose', () => {
    viewport.dispose();
    expect(binding.disconnect).to.be.calledOnce;
  });

  it('should connect binding later when visibility changes', () => {
    binding.connect = sandbox.spy();
    binding.disconnect = sandbox.spy();
    viewer.isVisible = () => false;
    let onVisibilityHandler;
    viewer.onVisibilityChanged = handler => onVisibilityHandler = handler;
    viewport = new Viewport(ampdoc, binding, viewer);

    // Hasn't been called at first.
    expect(binding.connect).to.not.be.called;
    expect(binding.disconnect).to.not.be.called;
    expect(viewport.size_).to.be.null;

    // When becomes visible - it gets called.
    viewer.isVisible = () => true;
    onVisibilityHandler();
    expect(binding.connect).to.be.calledOnce;
    expect(binding.disconnect).to.not.be.called;

    // Repeat visibility calls do not affect anything.
    onVisibilityHandler();
    expect(binding.connect).to.be.calledOnce;
    expect(binding.disconnect).to.not.be.called;

    // When becomes invisible - it gets disconnected.
    viewer.isVisible = () => false;
    onVisibilityHandler();
    expect(binding.connect).to.be.calledOnce;
    expect(binding.disconnect).to.be.calledOnce;
  });

  it('should resize only after size has been initialed', () => {
    binding.connect = sandbox.spy();
    binding.disconnect = sandbox.spy();
    viewer.isVisible = () => true;
    let onVisibilityHandler;
    viewer.onVisibilityChanged = handler => onVisibilityHandler = handler;
    viewport = new Viewport(ampdoc, binding, viewer);

    // Size has not be initialized yet.
    expect(binding.connect).to.be.calledOnce;
    expect(binding.disconnect).to.not.be.called;
    expect(viewport.size_).to.be.null;

    // Disconnect: ignore resizing.
    viewer.isVisible = () => false;
    onVisibilityHandler();
    expect(binding.connect).to.be.calledOnce;
    expect(binding.disconnect).to.be.calledOnce;
    expect(viewport.size_).to.be.null;

    // Size has been initialized.
    viewport.size_ = {width: 0, height: 0};
    viewer.isVisible = () => true;
    onVisibilityHandler();
    expect(binding.connect).to.be.calledTwice;
    expect(binding.disconnect).to.be.calledOnce;
    expect(viewport.size_).to.deep.equal(viewportSize);
  });

  it('should pass through size and scroll', () => {
    expect(viewport.getPaddingTop()).to.equal(19);
    expect(updatedPaddingTop).to.equal(19);
    expect(viewport.getSize().width).to.equal(111);
    expect(viewport.getSize().height).to.equal(222);
    expect(viewport.getTop()).to.equal(17);
    expect(viewport.getRect().left).to.equal(0);
    expect(viewport.getRect().top).to.equal(17);
    expect(viewport.getRect().width).to.equal(111);
    expect(viewport.getRect().height).to.equal(222);
  });

  it('should cache result for getRect()', () => {
    assert.strictEqual(viewport.getRect(), viewport.getRect());
  });

  it('should invalidate getRect() cache after scroll', () => {
    expect(viewport.getRect().top).to.equal(17);

    // Scroll vertically.
    binding.getScrollTop = () => 44;
    viewport.scroll_();

    expect(viewport.getRect().top).to.equal(44);
  });

  it('should invalidate getRect() cache after resize', () => {
    expect(viewport.getRect().width).to.equal(111);

    // Resize horizontally.
    viewportSize.width = 112;
    viewport.resize_();

    expect(viewport.getRect().width).to.equal(112);
  });

  it('should not relayout on height resize', () => {
    let changeEvent = null;
    viewport.onChanged(event => {
      changeEvent = event;
    });
    viewportSize.height = 223;
    viewport.resize_();
    expect(changeEvent).to.not.equal(null);
    expect(changeEvent.relayoutAll).to.equal(false);
    expect(changeEvent.velocity).to.equal(0);
  });

  it('should relayout on width resize', () => {
    let changeEvent = null;
    viewport.onChanged(event => {
      changeEvent = event;
    });
    viewportSize.width = 112;
    viewport.resize_();
    expect(changeEvent).to.not.equal(null);
    expect(changeEvent.relayoutAll).to.equal(true);
    expect(changeEvent.velocity).to.equal(0);
  });

  it('should defer change event until fixed layer is complete', () => {
    let changeEvent = null;
    viewport.onChanged(event => {
      changeEvent = event;
    });
    let fixedResolver;
    const fixedPromise = new Promise(resolve => fixedResolver = resolve);
    viewport.fixedLayer_ = {update: () => fixedPromise};
    viewportSize.width = 112;
    viewport.resize_();
    expect(changeEvent).to.be.null;
    fixedResolver();
    return fixedPromise.then(() => {
      expect(changeEvent).to.not.be.null;
    });
  });

  it('should not do anything if padding is not changed', () => {
    const bindingMock = sandbox.mock(binding);
    viewerViewportHandler({paddingTop: 19});
    bindingMock.verify();
  });

  it('should update non-transient padding', () => {
    const bindingMock = sandbox.mock(binding);
    const fixedLayerMock = sandbox.mock(viewport.fixedLayer_);
    fixedLayerMock.expects('updatePaddingTop')
        .withExactArgs(/* paddingTop */ 0, /* transient */ undefined)
        .once();
    viewerViewportHandler({paddingTop: 0});
    bindingMock.verify();
    fixedLayerMock.verify();
  });

  it('should update padding when viewer wants to hide header', () => {
    const bindingMock = sandbox.mock(binding);
    const fixedLayerMock = sandbox.mock(viewport.fixedLayer_);
    fixedLayerMock.expects('updatePaddingTop')
        .withExactArgs(/* paddingTop */ 0, /* transient */ true)
        .once();
    bindingMock.expects('hideViewerHeader').withArgs(true, 19).once();
    viewerViewportHandler({paddingTop: 0, duation: 300, curve: 'ease-in',
        transient: true});
    bindingMock.verify();
    fixedLayerMock.verify();
  });

  it('should update padding for fixed layer when viewer wants to ' +
      'hide header', () => {
    viewport.fixedLayer_ = {updatePaddingTop: () => {}};
    const fixedLayerMock = sandbox.mock(viewport.fixedLayer_);
    fixedLayerMock.expects('updatePaddingTop').withArgs(0).once();
    viewerViewportHandler({paddingTop: 0, duation: 300, curve: 'ease-in',
        transient: 'true'});
    fixedLayerMock.verify();
  });

  it('should update viewport when entering lightbox mode', () => {
    viewport.vsync_ = {mutate: callback => callback()};
    const enterOverlayModeStub = sandbox.stub(viewport, 'enterOverlayMode');
    const hideFixedLayerStub = sandbox.stub(viewport, 'hideFixedLayer');
    const bindingMock = sandbox.mock(binding);
    bindingMock.expects('updateLightboxMode').withArgs(true).once();

    viewport.enterLightboxMode();

    bindingMock.verify();
    expect(enterOverlayModeStub).to.be.calledOnce;
    expect(hideFixedLayerStub).to.be.calledOnce;

    expect(viewer.sendMessage).to.have.been.calledOnce;
    expect(viewer.sendMessage).to.have.been.calledWith('requestFullOverlay',
        {}, true);
  });

  it('should update viewport when leaving lightbox mode', () => {
    viewport.vsync_ = {mutate: callback => callback()};
    const leaveOverlayModeStub = sandbox.stub(viewport, 'leaveOverlayMode');
    const showFixedLayerStub = sandbox.stub(viewport, 'showFixedLayer');
    const bindingMock = sandbox.mock(binding);
    bindingMock.expects('updateLightboxMode').withArgs(false).once();

    viewport.leaveLightboxMode();

    bindingMock.verify();
    expect(leaveOverlayModeStub).to.be.calledOnce;
    expect(showFixedLayerStub).to.be.calledOnce;

    expect(viewer.sendMessage).to.have.been.calledOnce;
    expect(viewer.sendMessage).to.have.been.calledWith('cancelFullOverlay',
        {}, true);
  });

  it('should update viewport when entering overlay mode', () => {
    const disableTouchZoomStub = sandbox.stub(viewport, 'disableTouchZoom');
    const disableScrollStub = sandbox.stub(viewport, 'disableScroll');

    viewport.enterOverlayMode();

    expect(disableTouchZoomStub).to.be.calledOnce;
    expect(disableScrollStub).to.be.calledOnce;
  });

  it('should update viewport when leaving overlay mode', () => {
    const restoreOriginalTouchZoomStub = sandbox.stub(viewport,
        'restoreOriginalTouchZoom');
    const resetScrollStub = sandbox.stub(viewport, 'resetScroll');

    viewport.leaveOverlayMode();

    expect(restoreOriginalTouchZoomStub).to.be.calledOnce;
    expect(resetScrollStub).to.be.calledOnce;
  });

  it('should send scroll events', () => {
    // 0         ->    6     ->      12   ->      16         ->   18
    // scroll-10    scroll-20    scroll-30   2nd anim frame    scroll-40

    // when there's no scroll
    expect(viewport.scrollAnimationFrameThrottled_).to.be.false;
    expect(viewer.sendMessage).to.not.have.been.called;
    // scroll to 10
    viewport.getScrollTop = () => 10;
    viewport.sendScrollMessage_();
    expect(viewport.scrollAnimationFrameThrottled_).to.be.true;
    expect(viewer.sendMessage).to.not.have.been.called;
    // 6 ticks later, still during first animation frame
    clock.tick(6);
    expect(viewport.scrollAnimationFrameThrottled_).to.be.true;
    // scroll to 20
    viewport.getScrollTop = () => 20;
    viewport.sendScrollMessage_();
    expect(viewport.scrollAnimationFrameThrottled_).to.be.true;
    expect(viewer.sendMessage).to.not.have.been.called;
    // 6 ticks later, still during first animation frame
    clock.tick(6);
    expect(viewport.scrollAnimationFrameThrottled_).to.be.true;
    // scroll to 30
    viewport.getScrollTop = () => 30;
    viewport.sendScrollMessage_();
    expect(viewport.scrollAnimationFrameThrottled_).to.be.true;
    expect(viewer.sendMessage).to.not.have.been.called;
    // 6 ticks later, second animation frame starts
    clock.tick(6);
    expect(viewport.scrollAnimationFrameThrottled_).to.be.false;
    expect(viewer.sendMessage).to.have.been.calledOnce;
    expect(viewer.sendMessage).to.have.been.calledWith('scroll',
        {scrollTop: 30}, true);
    // scroll to 40
    viewport.getScrollTop = () => 40;
    viewport.sendScrollMessage_();
    expect(viewport.scrollAnimationFrameThrottled_).to.be.true;
    expect(viewer.sendMessage).to.have.been.calledOnce;
  });

  it('should defer scroll events', () => {
    let changeEvent = null;
    let eventCount = 0;
    viewport.onChanged(event => {
      changeEvent = event;
      eventCount++;
    });
    // when there's no scroll
    expect(viewport.scrollTracking_).to.be.false;
    expect(viewer.sendMessage).to.not.have.been.called;

    // time 0: scroll to 34
    // raf for viewer.postScroll, delay 36 ticks till raf for throttledScroll_
    binding.getScrollTop = () => 34;
    viewport.scroll_();
    expect(viewport.scrollTracking_).to.be.true;
    viewport.scroll_();
    viewport.scroll_();
    expect(changeEvent).to.equal(null);
    expect(viewport.scrollTracking_).to.be.true;

    clock.tick(8);
    expect(changeEvent).to.equal(null);
    clock.tick(8);
    // time 16: scroll to 35
    // call viewer.postScroll, raf for viewer.postScroll
    expect(changeEvent).to.equal(null);
    expect(viewer.sendMessage).to.have.callCount(1);
    expect(viewer.sendMessage).to.have.been.calledWith('scroll',
        {scrollTop: 34}, true);
    binding.getScrollTop = () => 35;
    viewport.scroll_();

    clock.tick(16);
    // time 32: scroll to 35
    // call viewer.postScroll, raf for viewer.postScroll
    viewport.scroll_();
    expect(changeEvent).to.equal(null);
    expect(viewport.scrollTracking_).to.be.true;
    expect(viewer.sendMessage).to.have.callCount(2);

    // time 36:
    // raf for throttledScroll_

    clock.tick(16);
    // time 48: scroll to 35
    // call viewer.postScroll, call throttledScroll_
    // raf for viewer.postScroll
    // delay 36 ticks till raf for throttledScroll_
    expect(viewport.scrollTracking_).to.be.false;
    viewport.scroll_();
    expect(changeEvent).to.not.equal(null);
    expect(changeEvent.relayoutAll).to.equal(false);
    expect(changeEvent.velocity).to.be.closeTo(0.020833, 1e-4);
    expect(eventCount).to.equal(1);
    expect(viewport.scrollTracking_).to.be.true;
    expect(viewer.sendMessage).to.have.callCount(3);
    changeEvent = null;

    clock.tick(16);
    // time 64:
    // call viewer.postScroll
    expect(viewer.sendMessage).to.have.callCount(4);

    clock.tick(20);
    // time 84:
    // raf for throttledScroll_

    clock.tick(16);
    // time 100:
    // call throttledScroll_
    expect(changeEvent).to.not.equal(null);
    expect(changeEvent.relayoutAll).to.equal(false);
    expect(viewport.scrollTracking_).to.be.false;
    expect(changeEvent.velocity).to.be.equal(0);
    expect(eventCount).to.equal(2);
  });

  it('should update scroll pos and reset cache', () => {
    const bindingMock = sandbox.mock(binding);
    bindingMock.expects('setScrollTop').withArgs(117).once();
    viewport.setScrollTop(117);
    expect(viewport./*OK*/scrollTop_).to.be.null;
  });

  it('should change scrollTop for scrollIntoView and respect padding', () => {
    const element = document.createElement('div');
    const bindingMock = sandbox.mock(binding);
    bindingMock.expects('getLayoutRect').withArgs(element)
        .returns({top: 111}).once();
    bindingMock.expects('setScrollTop').withArgs(111 - /* padding */ 19).once();
    viewport.scrollIntoView(element);
    bindingMock.verify();
  });

  it('should change scrollTop for animateScrollIntoView and respect ' +
    'padding', () => {
    const element = document.createElement('div');
    const bindingMock = sandbox.mock(binding);
    bindingMock.expects('getLayoutRect').withArgs(element)
        .returns({top: 111}).once();
    bindingMock.expects('setScrollTop').withArgs(111 - /* padding */ 19).once();
    const duration = 1000;
    const promise = viewport.animateScrollIntoView(element, 1000).then(() => {
      bindingMock.verify();
    });
    clock.tick(duration);
    runVsync();
    return promise;
  });

  it('should not change scrollTop for animateScrollIntoView', () => {
    const element = document.createElement('div');
    const bindingMock = sandbox.mock(binding);
    bindingMock.expects('getLayoutRect').withArgs(element)
        .returns({top: 111}).once();
    viewport.paddingTop_ = 0;
    sandbox.stub(viewport, 'getScrollTop').returns(111);
    bindingMock.expects('setScrollTop').withArgs(111).never();
    const duration = 1000;
    const promise = viewport.animateScrollIntoView(element, 1000).then(() => {
      bindingMock.verify();
    });
    clock.tick(duration);
    runVsync();
    return promise;
  });

  it('should send cached scroll pos to getLayoutRect', () => {
    const element = document.createElement('div');
    const bindingMock = sandbox.mock(binding);
    viewport.scrollTop_ = 111;
    viewport.scrollLeft_ = 222;
    bindingMock.expects('getLayoutRect').withArgs(element, 222, 111)
        .returns('sentinel').once();
    expect(viewport.getLayoutRect(element)).to.equal('sentinel');
  });

  it('should deletegate scrollWidth', () => {
    const bindingMock = sandbox.mock(binding);
    bindingMock.expects('getScrollWidth').withArgs().returns(111).once();
    expect(viewport.getScrollWidth()).to.equal(111);
  });

  it('should deletegate scrollHeight', () => {
    const bindingMock = sandbox.mock(binding);
    bindingMock.expects('getScrollHeight').withArgs().returns(117).once();
    expect(viewport.getScrollHeight()).to.equal(117);
  });

  it('should add class to HTML element with make-body-block experiment', () => {
    viewer.isEmbedded = () => true;
    toggleExperiment(windowApi, 'make-body-block', true);
    const docElement = windowApi.document.documentElement;
    const addStub = sandbox.stub(docElement.classList, 'add');
    viewport = new Viewport(ampdoc, binding, viewer);
    expect(addStub).to.be.calledWith('i-amphtml-make-body-block');
  });

  it('should scroll to target position when the viewer sets scrollTop', () => {
    const bindingMock = sandbox.mock(binding);
    bindingMock.expects('setScrollTop').withArgs(117).once();
    viewerScrollDocHandler({scrollTop: 117});
    bindingMock.verify();
  });

  describes.realWin('top-level styles', {amp: 1}, env => {
    let win;
    let root;

    beforeEach(() => {
      win = env.win;
      ampdoc = new AmpDocSingle(win);
      root = win.document.documentElement;
      root.className = '';
    });

    it('should not set pan-y when not embedded', () => {
      viewer.isEmbedded = () => false;
      viewport = new Viewport(ampdoc, binding, viewer);
      expect(win.getComputedStyle(root)['touch-action']).to.equal('auto');
    });

    it('should set pan-y with experiment', () => {
      viewer.isEmbedded = () => true;
      viewport = new Viewport(ampdoc, binding, viewer);
      expect(win.getComputedStyle(root)['touch-action']).to.equal('pan-y');
    });
  });

  describe('for child window', () => {
    let viewport;
    let bindingMock;
    let iframe;
    let iframeWin;
    let ampdoc;

    beforeEach(() => {
      ampdoc = new AmpDocSingle(window);
      viewport = new Viewport(ampdoc, binding, viewer);
      bindingMock = sandbox.mock(binding);
      iframe = document.createElement('iframe');
      const html = '<div id="one"></div>';
      let promise;
      if ('srcdoc' in iframe) {
        iframe.srcdoc = html;
        promise = loadPromise(iframe);
        document.body.appendChild(iframe);
      } else {
        iframe.src = 'about:blank';
        document.body.appendChild(iframe);
        const childDoc = iframe.contentWindow.document;
        childDoc.open();
        childDoc.write(html);
        childDoc.close();
        promise = Promise.resolve();
      }
      return promise.then(() => {
        iframeWin = iframe.contentWindow;
        setParentWindow(iframeWin, window);
      });
    });

    afterEach(() => {
      if (iframe.parentElement) {
        iframe.parentElement.removeChild(iframe);
      }
      bindingMock.verify();
    });

    it('should calculate child window element rect via parent', () => {
      viewport.scrollLeft_ = 0;
      viewport.scrollTop_ = 0;
      const element = iframeWin.document.createElement('div');
      iframeWin.document.body.appendChild(element);
      bindingMock.expects('getLayoutRect')
          .withExactArgs(element, 0, 0)
          .returns({left: 20, top: 10}).once();
      bindingMock.expects('getLayoutRect')
          .withExactArgs(iframe, 0, 0)
          .returns({left: 211, top: 111}).once();

      const rect = viewport.getLayoutRect(element);
      expect(rect.left).to.equal(211 + 20);
      expect(rect.top).to.equal(111 + 10);
    });

    it('should offset child window element with parent scroll pos', () => {
      viewport.scrollLeft_ = 200;
      viewport.scrollTop_ = 100;
      const element = iframeWin.document.createElement('div');
      iframeWin.document.body.appendChild(element);
      bindingMock.expects('getLayoutRect')
          .withExactArgs(element, 0, 0)
          .returns({left: 20, top: 10}).once();
      bindingMock.expects('getLayoutRect')
          .withExactArgs(iframe, 200, 100)
          .returns({left: 211, top: 111}).once();

      const rect = viewport.getLayoutRect(element);
      expect(rect.left).to.equal(211 + 20);
      expect(rect.top).to.equal(111 + 10);
    });
  });
});

describe('Viewport META', () => {

  describe('parseViewportMeta', () => {
    it('should accept null or empty strings', () => {
      expect(parseViewportMeta(null)).to.be.empty;
    });
    it('should parse single key-value', () => {
      expect(parseViewportMeta('width=device-width')).to.deep.equal({
        'width': 'device-width',
      });
    });
    it('should parse two key-values', () => {
      expect(parseViewportMeta('width=device-width,minimum-scale=1')).to.deep
          .equal({
            'width': 'device-width',
            'minimum-scale': '1',
          });
    });
    it('should parse empty value', () => {
      expect(parseViewportMeta('width=device-width,minimal-ui')).to.deep.equal({
        'width': 'device-width',
        'minimal-ui': '',
      });
      expect(parseViewportMeta('minimal-ui,width=device-width')).to.deep.equal({
        'width': 'device-width',
        'minimal-ui': '',
      });
    });
    it('should return last dupe', () => {
      expect(parseViewportMeta('width=100,width=200')).to.deep.equal({
        'width': '200',
      });
    });
    it('should ignore extra delims', () => {
      expect(parseViewportMeta(',,,width=device-width,,,,minimum-scale=1,,,'))
          .to.deep.equal({
            'width': 'device-width',
            'minimum-scale': '1',
          });
    });
    it('should support semicolon', () => {
      expect(parseViewportMeta('width=device-width;minimum-scale=1'))
          .to.deep.equal({
            'width': 'device-width',
            'minimum-scale': '1',
          });
    });
    it('should support mix of comma and semicolon', () => {
      expect(parseViewportMeta('width=device-width,minimum-scale=1;test=3;'))
          .to.deep.equal({
            'width': 'device-width',
            'minimum-scale': '1',
            'test': '3',
          });
    });
    it('should ignore extra mix delims', () => {
      expect(parseViewportMeta(',,;;,width=device-width;;,minimum-scale=1,,;'))
          .to.deep.equal({
            'width': 'device-width',
            'minimum-scale': '1',
          });
    });
  });

  describe('stringifyViewportMeta', () => {
    it('should stringify empty', () => {
      expect(stringifyViewportMeta({})).to.equal('');
    });
    it('should stringify single key-value', () => {
      expect(stringifyViewportMeta({'width': 'device-width'}))
          .to.equal('width=device-width');
    });
    it('should stringify two key-values', () => {
      const res = stringifyViewportMeta({
        'width': 'device-width',
        'minimum-scale': '1',
      });
      expect(res == 'width=device-width,minimum-scale=1' ||
          res == 'minimum-scale=1,width=device-width')
          .to.be.true;
    });
    it('should stringify empty values', () => {
      const res = stringifyViewportMeta({
        'width': 'device-width',
        'minimal-ui': '',
      });
      expect(res == 'width=device-width,minimal-ui' ||
          res == 'minimal-ui,width=device-width')
          .to.be.true;
    });
  });

  describe('updateViewportMetaString', () => {
    it('should do nothing with empty values', () => {
      expect(updateViewportMetaString(
          '', {})).to.equal('');
      expect(updateViewportMetaString(
          'width=device-width', {})).to.equal('width=device-width');
    });
    it('should add a new value', () => {
      expect(updateViewportMetaString(
          '', {'minimum-scale': '1'})).to.equal('minimum-scale=1');
      expect(parseViewportMeta(updateViewportMetaString(
          'width=device-width', {'minimum-scale': '1'})))
          .to.deep.equal({
            'width': 'device-width',
            'minimum-scale': '1',
          });
    });
    it('should replace the existing value', () => {
      expect(parseViewportMeta(updateViewportMetaString(
          'width=device-width,minimum-scale=2', {'minimum-scale': '1'})))
          .to.deep.equal({
            'width': 'device-width',
            'minimum-scale': '1',
          });
    });
    it('should delete the existing value', () => {
      expect(parseViewportMeta(updateViewportMetaString(
          'width=device-width,minimum-scale=1', {'minimum-scale': undefined})))
          .to.deep.equal({
            'width': 'device-width',
          });
    });
    it('should ignore delete for a non-existing value', () => {
      expect(parseViewportMeta(updateViewportMetaString(
          'width=device-width', {'minimum-scale': undefined})))
          .to.deep.equal({
            'width': 'device-width',
          });
    });
    it('should do nothing if values did not change', () => {
      expect(updateViewportMetaString(
          'width=device-width,minimum-scale=1', {'minimum-scale': '1'}))
          .to.equal('width=device-width,minimum-scale=1');
    });
  });

  describe('TouchZoom', () => {
    let sandbox;
    let clock;
    let viewport;
    let binding;
    let viewer;
    let windowApi;
    let ampdoc;
    let originalViewportMetaString, viewportMetaString;
    let viewportMeta;
    let viewportMetaSetter;

    beforeEach(() => {
      sandbox = sinon.sandbox.create();
      clock = sandbox.useFakeTimers();
      viewer = {
        isEmbedded: () => false,
        getParam: param => {
          if (param == 'paddingTop') {
            return 0;
          }
          return undefined;
        },
        onMessage: () => {},
        isVisible: () => true,
        onVisibilityChanged: () => {},
      };

      originalViewportMetaString = 'width=device-width,minimum-scale=1';
      viewportMetaString = originalViewportMetaString;
      viewportMeta = Object.create(null);
      viewportMetaSetter = sandbox.spy();
      Object.defineProperty(viewportMeta, 'content', {
        get: () => viewportMetaString,
        set: value => {
          viewportMetaSetter(value);
          viewportMetaString = value;
        },
      });
      windowApi = {
        document: {
          documentElement: {
            style: {},
            classList: {
              add: function() {},
            },
          },
          querySelector: selector => {
            if (selector == 'meta[name=viewport]') {
              return viewportMeta;
            }
            return undefined;
          },
        },
        navigator: window.navigator,
        setTimeout: window.setTimeout,
        clearTimeout: window.clearTimeout,
        location: {},
      };
      const ampdocService = installDocService(windowApi,
          /* isSingleDoc */ true);
      ampdoc = ampdocService.getAmpDoc();
      installTimerService(windowApi);
      installPlatformService(windowApi);
      installViewerServiceForDoc(ampdoc);
      binding = new ViewportBindingDef();
      viewport = new Viewport(ampdoc, binding, viewer);
    });

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

    it('should initialize original viewport meta', () => {
      viewport.getViewportMeta_();
      expect(viewport.originalViewportMetaString_).to.equal(viewportMetaString);
      expect(viewportMetaSetter).to.have.not.been.called;
    });

    it('should disable TouchZoom', () => {
      viewport.disableTouchZoom();
      expect(viewportMetaSetter).to.be.calledOnce;
      expect(viewportMetaString).to.have.string('maximum-scale=1');
      expect(viewportMetaString).to.have.string('user-scalable=no');
    });

    it('should ignore disable TouchZoom if already disabled', () => {
      viewportMetaString = 'width=device-width,minimum-scale=1,' +
          'maximum-scale=1,user-scalable=no';
      viewport.disableTouchZoom();
      expect(viewportMetaSetter).to.have.not.been.called;
    });

    it('should ignore disable TouchZoom if embedded', () => {
      windowApi.parent = {};
      viewport.disableTouchZoom();
      expect(viewportMetaSetter).to.have.not.been.called;
    });

    it('should restore TouchZoom', () => {
      viewport.disableTouchZoom();
      expect(viewportMetaSetter).to.be.calledOnce;
      expect(viewportMetaString).to.have.string('maximum-scale=1');
      expect(viewportMetaString).to.have.string('user-scalable=no');

      viewport.restoreOriginalTouchZoom();
      expect(viewportMetaSetter).to.have.callCount(2);
      expect(viewportMetaString).to.equal(originalViewportMetaString);
    });

    it('should reset TouchZoom; zooming state unknown', () => {
      viewport.resetTouchZoom();
      expect(viewportMetaSetter).to.be.calledOnce;
      expect(viewportMetaString).to.have.string('maximum-scale=1');
      expect(viewportMetaString).to.have.string('user-scalable=no');

      clock.tick(1000);
      expect(viewportMetaSetter).to.have.callCount(2);
      expect(viewportMetaString).to.equal(originalViewportMetaString);
    });

    it('should ignore reset TouchZoom if not currently zoomed', () => {
      windowApi.document.documentElement.clientHeight = 500;
      windowApi.innerHeight = 500;
      viewport.resetTouchZoom();
      expect(viewportMetaSetter).to.have.not.been.called;
    });

    it('should proceed with reset TouchZoom if currently zoomed', () => {
      windowApi.document.documentElement.clientHeight = 500;
      windowApi.innerHeight = 300;
      viewport.resetTouchZoom();
      expect(viewportMetaSetter).to.be.calledOnce;
    });

    it('should ignore reset TouchZoom if embedded', () => {
      windowApi.parent = {};
      viewport.resetTouchZoom();
      expect(viewportMetaSetter).to.have.not.been.called;
    });
  });
});

describes.realWin('ViewportBindingNatural', {ampCss: true}, env => {
  let binding;
  let win;
  let viewer;

  beforeEach(() => {
    env.iframe.style.width = '100px';
    env.iframe.style.height = '200px';
    win = env.win;
    const child = win.document.createElement('div');
    child.style.width = '200px';
    child.style.height = '300px';
    win.document.body.appendChild(child);

    viewer = {};

    installPlatformService(win);

    binding = new ViewportBindingNatural_(win, viewer);
    binding.connect();
  });

  afterEach(() => {
    toggleExperiment(win, 'make-body-relative', false);
  });

  it('should setup overflow:visible on body', () => {
    expect(win.document.body.style.overflow).to.equal('visible');
  });

  it('should configure make-body-relative', () => {
    toggleExperiment(win, 'make-body-relative', true);
    binding = new ViewportBindingNatural_(win, viewer);
    expect(win.document.body.style.display).to.not.be.ok;
    expect(win.document.body.style.position).to.equal('relative');
    // It's important that this experiment does NOT override the previously
    // set `overflow`.
    expect(win.document.body.style.overflow).to.equal('visible');
  });

  it('should NOT require fixed layer transferring', () => {
    expect(binding.requiresFixedLayerTransfer()).to.be.false;
  });

  it('should connect events: subscribe to scroll and resize events', () => {
    expect(win.eventListeners.count('resize')).to.equal(1);
    expect(win.eventListeners.count('scroll')).to.equal(1);
  });

  it('should disconnect events', () => {
    // After disconnect, there are no more listeners on window.
    binding.disconnect();
    expect(win.eventListeners.count('resize')).to.equal(0);
    expect(win.eventListeners.count('scroll')).to.equal(0);
  });

  it('should update padding', () => {
    binding.updatePaddingTop(31);
    expect(win.document.documentElement.style.paddingTop).to.equal('31px');
  });

  it('should calculate size', () => {
    const size = binding.getSize();
    expect(size.width).to.equal(100);
    expect(size.height).to.equal(200);
  });

  it('should calculate scrollTop from scrollElement', () => {
    win.pageYOffset = 11;
    win.document.scrollingElement.scrollTop = 17;
    expect(binding.getScrollTop()).to.equal(17);
  });

  it('should calculate scrollWidth from scrollElement', () => {
    expect(binding.getScrollWidth()).to.equal(200);
  });

  it('should calculate scrollHeight from scrollElement', () => {
    expect(binding.getScrollHeight()).to.equal(300);
  });

  it('should update scrollTop on scrollElement', () => {
    win.pageYOffset = 11;
    win.document.scrollingElement.scrollTop = 17;
    binding.setScrollTop(21);
    expect(win.document.scrollingElement./*OK*/scrollTop).to.equal(21);
  });

  it('should fallback scrollTop to pageYOffset', () => {
    win.pageYOffset = 11;
    delete win.document.scrollingElement.scrollTop;
    expect(binding.getScrollTop()).to.equal(11);
  });

  it('should offset client rect for layout', () => {
    win.pageXOffset = 0;
    win.pageYOffset = 200;
    delete win.document.scrollingElement;
    const el = {
      getBoundingClientRect: () => {
        return {left: 11.5, top: 12.5, width: 13.5, height: 14.5};
      },
    };
    const rect = binding.getLayoutRect(el);
    expect(rect.left).to.equal(12);  // round(0 + 11.5)
    expect(rect.top).to.equal(213);  // round(200 + 12.5)
    expect(rect.width).to.equal(14);  // round(13.5)
    expect(rect.height).to.equal(15);  // round(14.5)
  });

  it('should offset client rect for layout and position passed in', () => {
    win.pageXOffset = 0;
    win.pageYOffset = 2000;
    delete win.document.scrollingElement;
    const el = {
      getBoundingClientRect: () => {
        return {left: 11.5, top: 12.5, width: 13.5, height: 14.5};
      },
    };
    const rect = binding.getLayoutRect(el, 100, 200);
    expect(rect.left).to.equal(112);  // round(100 + 11.5)
    expect(rect.top).to.equal(213);  // round(200 + 12.5)
    expect(rect.width).to.equal(14);  // round(13.5)
    expect(rect.height).to.equal(15);  // round(14.5)
  });

  it('should disable scroll temporarily and reset scroll', () => {
    let htmlCss = win.getComputedStyle(win.document.documentElement);
    expect(htmlCss.overflowX).to.equal('hidden');
    expect(htmlCss.overflowY).to.equal('auto');

    binding.disableScroll();

    expect(win.document.documentElement).to.have.class(
        'i-amphtml-scroll-disabled');
    htmlCss = win.getComputedStyle(win.document.documentElement);
    expect(htmlCss.overflowX).to.equal('hidden');
    expect(htmlCss.overflowY).to.equal('hidden');

    binding.resetScroll();

    expect(win.document.documentElement).to.not.have.class(
        'i-amphtml-scroll-disabled');
    htmlCss = win.getComputedStyle(win.document.documentElement);
    expect(htmlCss.overflowX).to.equal('hidden');
    expect(htmlCss.overflowY).to.equal('auto');
  });
});

describes.realWin('ViewportBindingNaturalIosEmbed', {}, env => {
  let binding;
  let win;

  beforeEach(() => {
    env.iframe.style.width = '100px';
    env.iframe.style.height = '200px';
    win = env.win;
    const child = win.document.createElement('div');
    child.id = 'child';
    child.style.width = '200px';
    child.style.height = '300px';
    win.document.body.appendChild(child);
    const ampdocService = installDocService(win, /* isSingleDoc */ true);
    const ampdoc = ampdocService.getAmpDoc();

    installPlatformService(win);
    installViewerServiceForDoc(ampdoc);
    installVsyncService(win);

    binding = new ViewportBindingNaturalIosEmbed_(win, ampdoc);
    return Promise.resolve();
  });

  it('should require fixed layer transferring', () => {
    expect(binding.requiresFixedLayerTransfer()).to.be.true;
  });

  it('should subscribe to resize events on window, scroll on body', () => {
    expect(win.eventListeners.count('resize')).to.equal(1);
    expect(win.eventListeners.count('scroll')).to.equal(0);
    expect(win.document.body.eventListeners.count('scroll')).to.equal(1);
  });

  it('should setup document for embed scrolling', () => {
    const documentElement = win.document.documentElement;
    const body = win.document.body;
    expect(documentElement.style.overflowY).to.equal('auto');
    expect(documentElement.style.webkitOverflowScrolling).to.equal('touch');
    expect(body.style.overflowX).to.equal('hidden');
    expect(body.style.overflowY).to.equal('auto');
    expect(body.style.webkitOverflowScrolling).to.equal('touch');
    expect(body.style.position).to.equal('absolute');
    expect(body.style.top).to.equal('0px');
    expect(body.style.left).to.equal('0px');
    expect(body.style.right).to.equal('0px');
    expect(body.style.bottom).to.equal('0px');

    const scrollpos = body.querySelector('#i-amphtml-scrollpos');
    expect(scrollpos).to.be.ok;
    expect(scrollpos.style.position).to.equal('absolute');
    expect(scrollpos.style.top).to.equal('0px');
    expect(scrollpos.style.left).to.equal('0px');
    expect(scrollpos.style.width).to.equal('0px');
    expect(scrollpos.style.height).to.equal('0px');
    expect(scrollpos.style.visibility).to.equal('hidden');

    const scrollmove = body.querySelector('#i-amphtml-scrollmove');
    expect(scrollmove).to.be.ok;
    expect(scrollmove.style.position).to.equal('absolute');
    expect(scrollmove.style.top).to.equal('0px');
    expect(scrollmove.style.left).to.equal('0px');
    expect(scrollmove.style.width).to.equal('0px');
    expect(scrollmove.style.height).to.equal('0px');
    expect(scrollmove.style.visibility).to.equal('hidden');

    const endpos = body.querySelector('#i-amphtml-endpos');
    expect(endpos).to.be.ok;
    expect(endpos.style.position).to.not.be.ok;
    expect(endpos.style.top).to.not.be.ok;
    expect(endpos.style.width).to.equal('0px');
    expect(endpos.style.height).to.equal('0px');
    expect(endpos.style.visibility).to.equal('hidden');
    expect(endpos.getBoundingClientRect().top).to.equal(300);
  });

  it('should always have scrollWidth equal window.innerWidth', () => {
    expect(binding.getScrollWidth()).to.equal(100);
  });

  it('should update border on BODY when updatePaddingTop', () => {
    binding.updatePaddingTop(31);
    expect(win.document.body.style.borderTop).to
        .equal('31px solid transparent');
    expect(win.document.body.style.paddingTop).to.not.be.ok;
  });

  it('should update border in lightbox mode when updateLightboxMode', () => {
    binding.updatePaddingTop(31);
    expect(win.document.body.style.borderTop).to
        .equal('31px solid transparent');
    expect(win.document.body.style.borderTopStyle).to.equal('solid');

    binding.updateLightboxMode(true);
    expect(win.document.body.style.borderTopStyle).to.equal('none');

    binding.updateLightboxMode(false);
    expect(win.document.body.style.borderTopStyle).to.equal('solid');
    expect(win.document.body.style.borderBottomStyle).to.not.equal('solid');
    expect(win.document.body.style.borderLeftStyle).to.not.equal('solid');
    expect(win.document.body.style.borderRightStyle).to.not.equal('solid');
  });

  it('should calculate size', () => {
    const size = binding.getSize();
    expect(size.width).to.equal(100);
    expect(size.height).to.equal(200);
  });

  it('should calculate scrollTop from scrollpos element', () => {
    binding.setScrollTop(17);
    const scrollpos = win.document.body.querySelector('#i-amphtml-scrollpos');
    expect(scrollpos.getBoundingClientRect().top).to.equal(-17);
    binding.onScrolled_();
    expect(binding.getScrollTop()).to.equal(17);
  });

  it('should calculate scrollTop from scrollpos element with padding', () => {
    binding.setScrollTop(17);
    const scrollpos = win.document.body.querySelector('#i-amphtml-scrollpos');
    expect(scrollpos.getBoundingClientRect().top).to.equal(-17);
    binding.updatePaddingTop(10);
    binding.onScrolled_();
    // scrollTop = - BCR.top + paddingTop
    expect(binding.getScrollTop()).to.equal(27);
  });

  it('should calculate scrollHeight from scrollpos/endpos elements', () => {
    binding.setScrollTop(17);
    const scrollpos = win.document.body.querySelector('#i-amphtml-scrollpos');
    expect(scrollpos.getBoundingClientRect().top).to.equal(-17);
    const endpos = win.document.body.querySelector('#i-amphtml-endpos');
    expect(endpos.getBoundingClientRect().top).to.equal(283);  // 300 - 17
    expect(binding.getScrollHeight()).to.equal(300);  // 283 - (-17)
  });

  it('should offset client rect for layout', () => {
    binding.setScrollTop(100);
    const scrollpos = win.document.body.querySelector('#i-amphtml-scrollpos');
    expect(scrollpos.getBoundingClientRect().top).to.equal(-100);
    expect(scrollpos.getBoundingClientRect().left).to.equal(0);
    binding.onScrolled_();
    const el = {
      getBoundingClientRect: () => {
        return {left: 11.5, top: 12.5, width: 13.5, height: 14.5};
      },
    };
    const rect = binding.getLayoutRect(el);
    expect(rect.left).to.equal(12);  // round(0 + 11.5)
    expect(rect.top).to.equal(113);  // round(100 + 12.5)
    expect(rect.width).to.equal(14);  // round(13.5)
    expect(rect.height).to.equal(15);  // round(14.5)
  });

  it('should set scroll position via moving element', () => {
    binding.setScrollTop(10);
    const scrollmove = win.document.body.querySelector('#i-amphtml-scrollmove');
    expect(scrollmove.style.transform).to.equal('translateY(10px)');
  });

  it('should set scroll position via moving element with padding', () => {
    binding.updatePaddingTop(19);
    binding.setScrollTop(10);
    const scrollmove = win.document.body.querySelector('#i-amphtml-scrollmove');
    // transform = scrollTop - paddingTop
    expect(scrollmove.style.transform).to.equal('translateY(-9px)');
  });

  it('should adjust scroll position when scrolled to 0', () => {
    const scrollpos = win.document.body.querySelector('#i-amphtml-scrollpos');
    expect(scrollpos.getBoundingClientRect().top).to.equal(0);
    const scrollmove = win.document.body.querySelector('#i-amphtml-scrollmove');
    const event = {preventDefault: sandbox.spy()};
    binding.adjustScrollPos_(event);
    expect(scrollmove.style.transform).to.equal('translateY(1px)');
    expect(scrollpos.getBoundingClientRect().top).to.equal(-1);
    expect(event.preventDefault).to.be.calledOnce;
  });

  it('should adjust scroll position when scrolled to 0 w/padding', () => {
    binding.updatePaddingTop(10);
    const scrollpos = win.document.body.querySelector('#i-amphtml-scrollpos');
    expect(scrollpos.getBoundingClientRect().top).to.equal(10);
    const scrollmove = win.document.body.querySelector('#i-amphtml-scrollmove');
    const event = {preventDefault: sandbox.spy()};
    binding.adjustScrollPos_(event);
    // transform = 1 - updatePadding
    expect(scrollmove.style.transform).to.equal('translateY(-9px)');
    // scroll pos should not change
    expect(scrollpos.getBoundingClientRect().top).to.equal(10);
    expect(event.preventDefault).to.be.calledOnce;
  });

  it('should adjust scroll position when scrolled to 0; w/o event', () => {
    const scrollpos = win.document.body.querySelector('#i-amphtml-scrollpos');
    expect(scrollpos.getBoundingClientRect().top).to.equal(0);
    const scrollmove = win.document.body.querySelector('#i-amphtml-scrollmove');
    binding.adjustScrollPos_();
    expect(scrollmove.style.transform).to.equal('translateY(1px)');
    expect(scrollpos.getBoundingClientRect().top).to.equal(-1);
  });

  it('should NOT adjust scroll position when scrolled away from 0', () => {
    binding.setScrollTop(10);
    const scrollpos = win.document.body.querySelector('#i-amphtml-scrollpos');
    expect(scrollpos.getBoundingClientRect().top).to.equal(-10);
    const event = {preventDefault: sandbox.spy()};
    binding.adjustScrollPos_(event);
    expect(scrollpos.getBoundingClientRect().top).to.equal(-10);
    expect(event.preventDefault).to.have.not.been.called;
  });

  it('should NOT adjust scroll position when overscrolled', () => {
    binding.setScrollTop(310);
    const event = {preventDefault: sandbox.spy()};
    binding.adjustScrollPos_(event);
    expect(event.preventDefault).to.have.not.been.called;
  });
});

describes.realWin('ViewportBindingIosEmbedWrapper', {ampCss: true}, env => {
  let win;
  let binding;
  let child;

  beforeEach(() => {
    env.iframe.style.width = '100px';
    env.iframe.style.height = '100px';
    win = env.win;
    win.document.documentElement.className = 'top';
    child = win.document.createElement('div');
    child.style.width = '200px';
    child.style.height = '300px';
    child.textContent = 'test';
    win.document.body.appendChild(child);
    binding = new ViewportBindingIosEmbedWrapper_(win);
    binding.connect();
  });

  it('should NOT require fixed layer transferring', () => {
    expect(binding.requiresFixedLayerTransfer()).to.be.true;
  });

  it('should have UI setup', () => {
    expect(binding.setupDone_).to.be.true;
    expect(win.document.documentElement)
        .to.have.class('i-amphtml-ios-embed');
    expect(win.document.body).to.exist;
    expect(win.document.body.parentNode)
        .to.not.equal(win.document.documentElement);
    expect(win.document.body.parentNode)
        .to.equal(binding.wrapper_);
    expect(binding.wrapper_.parentNode)
        .to.equal(win.document.documentElement);
    expect(binding.wrapper_.tagName).to.equal('HTML');
    expect(binding.wrapper_.id).to.equal('i-amphtml-wrapper');
    expect(win.document.body.contains(child)).to.be.true;
    expect(binding.wrapper_.contains(child)).to.be.true;
    expect(win.document.contains(child)).to.be.true;
    expect(child.textContent).to.equal('test');

    // Top-level classes moved to the wrapper element.
    expect(win.document.documentElement).to.not.have.class('top');
    expect(binding.wrapper_).to.have.class('top');
  });

  it('should have CSS setup', () => {
    win.document.body.style.display = 'table';
    const htmlCss = win.getComputedStyle(win.document.documentElement);
    const wrapperCss = win.getComputedStyle(binding.wrapper_);
    const bodyCss = win.getComputedStyle(win.document.body);

    // `<html>` must have `position: static` or layout is broken.
    expect(htmlCss.position).to.equal('static');

    // `<html>` and `<i-amphtml-wrapper>` must be scrollable, but not `body`.
    // Unfortunately, we can't test here `-webkit-overflow-scrolling`.
    expect(htmlCss.overflowY).to.equal('auto');
    expect(htmlCss.overflowX).to.equal('hidden');
    expect(wrapperCss.overflowY).to.equal('auto');
    expect(wrapperCss.overflowX).to.equal('hidden');
    expect(bodyCss.overflowY).to.equal('visible');
    expect(bodyCss.overflowX).to.equal('visible');

    // Wrapper must be a block and positioned absolute at 0/0/0/0.
    expect(wrapperCss.display).to.equal('block');
    expect(wrapperCss.position).to.equal('absolute');
    expect(wrapperCss.top).to.equal('0px');
    expect(wrapperCss.left).to.equal('0px');
    expect(wrapperCss.right).to.equal('0px');
    expect(wrapperCss.bottom).to.equal('0px');
    expect(wrapperCss.margin).to.equal('0px');

    // `body` must have `relative` positioning and `block` display.
    expect(bodyCss.position).to.equal('relative');
    // Preserve the customized `display` value.
    expect(bodyCss.display).to.equal('table');

    // `body` must have a 1px transparent border for two purposes:
    // (1) to cancel out margin collapse in body's children;
    // (2) to offset scroll adjustment to 1 to avoid scroll freeze problem.
    expect(bodyCss.borderTop.replace('rgba(0, 0, 0, 0)', 'transparent'))
        .to.equal('1px solid transparent');
    expect(bodyCss.margin).to.equal('0px');
  });

  it('should be immediately scrolled to 1 to avoid freeze', () => {
    expect(binding.wrapper_.scrollTop).to.equal(1);
  });

  it('should connect events: subscribe to scroll and resize events', () => {
    expect(win.eventListeners.count('resize')).to.equal(1);
    // Note that scroll event is on the wrapper, and NOT on root or body.
    expect(win.eventListeners.count('scroll')).to.equal(0);
    expect(win.document.eventListeners.count('scroll')).to.equal(0);
    expect(win.document.documentElement.eventListeners.count('scroll'))
        .to.equal(0);
    expect(win.document.body.eventListeners.count('scroll'))
        .to.equal(0);
  });

  it('should disconnect events', () => {
    // After disconnect, there are no more listeners on window.
    binding.disconnect();
    expect(win.eventListeners.count('resize')).to.equal(0);
  });

  it('should update padding', () => {
    binding.updatePaddingTop(31);
    expect(binding.wrapper_.style.paddingTop).to.equal('31px');
    // Notice, root is not touched.
    expect(win.document.documentElement.style.paddingTop).to.equal('');
  });

  it('should calculate size', () => {
    const size = binding.getSize();
    expect(size.width).to.equal(100);
    expect(size.height).to.equal(100);
  });

  it('should calculate scrollTop from wrapper', () => {
    binding.wrapper_.scrollTop = 17;
    expect(binding.getScrollTop()).to.equal(17);
  });

  it('should calculate scrollWidth from wrapper', () => {
    expect(binding.getScrollWidth()).to.equal(200);
  });

  it('should calculate scrollHeight from wrapper', () => {
    expect(binding.getScrollHeight()).to.equal(301); // +1px for border-top.
  });

  it('should update scrollTop on wrapper', () => {
    binding.setScrollTop(21);
    expect(binding.wrapper_.scrollTop).to.equal(21);
  });

  it('should adjust scrollTop to avoid scroll freeze', () => {
    binding.setScrollTop(21);
    expect(binding.wrapper_.scrollTop).to.equal(21);

    // `scrollTop=0` is normally not allowed.
    binding.setScrollTop(0);
    expect(binding.wrapper_.scrollTop).to.equal(1);
  });

  it('should offset client rect for layout', () => {
    binding.wrapper_.scrollTop = 200;
    const el = {
      getBoundingClientRect: () => {
        return {left: 11.5, top: 12.5, width: 13.5, height: 14.5};
      },
    };
    const rect = binding.getLayoutRect(el);
    expect(rect.top).to.equal(213);  // round(200 + 12.5)
    expect(rect.width).to.equal(14);  // round(13.5)
    expect(rect.height).to.equal(15);  // round(14.5)
  });

  it('should offset client rect for layout and position passed in', () => {
    binding.wrapper_.scrollTop = 10;
    const el = {
      getBoundingClientRect: () => {
        return {left: 11.5, top: 12.5, width: 13.5, height: 14.5};
      },
    };
    const rect = binding.getLayoutRect(el, 100, 200);
    expect(rect.left).to.equal(112);  // round(100 + 11.5)
    expect(rect.top).to.equal(213);  // round(200 + 12.5)
    expect(rect.width).to.equal(14);  // round(13.5)
    expect(rect.height).to.equal(15);  // round(14.5)
  });

  it('should call scroll event', () => {
    return new Promise(resolve => {
      binding.onScroll(resolve);
      binding.wrapper_.scrollTop = 11;
    }).then(() => {
      expect(binding.getScrollTop()).to.equal(11);
    });
  });

  it('should disable scroll temporarily and reset scroll', () => {
    let wrapperCss = win.getComputedStyle(binding.wrapper_);
    expect(wrapperCss.overflowX).to.equal('hidden');
    expect(wrapperCss.overflowY).to.equal('auto');

    binding.disableScroll();

    expect(binding.wrapper_).to.have.class('i-amphtml-scroll-disabled');
    wrapperCss = win.getComputedStyle(binding.wrapper_);
    expect(wrapperCss.overflowX).to.equal('hidden');
    expect(wrapperCss.overflowY).to.equal('hidden');

    binding.resetScroll();

    expect(binding.wrapper_).to.not.have.class(
        'i-amphtml-scroll-disabled');
    wrapperCss = win.getComputedStyle(binding.wrapper_);
    expect(wrapperCss.overflowX).to.equal('hidden');
    expect(wrapperCss.overflowY).to.equal('auto');
  });
});

describe('createViewport', () => {

  describes.fakeWin('in Android', {
    win: {navigator: {userAgent: 'Android'}},
  }, env => {
    let win;

    beforeEach(() => {
      win = env.win;
      installPlatformService(win);
      installTimerService(win);
    });

    it('should bind to "natural" when not iframed', () => {
      win.parent = win;
      const ampDoc = installDocService(win, true).getAmpDoc();
      installViewerServiceForDoc(ampDoc);
      const viewport = installViewportServiceForDoc(ampDoc);
      expect(viewport.binding_).to.be.instanceof(ViewportBindingNatural_);
    });

    it('should bind to "naturual" when iframed', () => {
      win.parent = {};
      const ampDoc = installDocService(win, true).getAmpDoc();
      installViewerServiceForDoc(ampDoc);
      const viewport = installViewportServiceForDoc(ampDoc);
      expect(viewport.binding_).to.be.instanceof(ViewportBindingNatural_);
    });
  });

  describes.fakeWin('in iOS', {
    win: {navigator: {userAgent: 'iPhone'}},
  }, env => {
    let win;

    beforeEach(() => {
      win = env.win;
      installPlatformService(win);
      installTimerService(win);
    });

    it('should bind to "natural" when not iframed', () => {
      win.parent = win;
      const ampDoc = installDocService(win, true).getAmpDoc();
      installViewerServiceForDoc(ampDoc);
      const viewport = installViewportServiceForDoc(ampDoc);
      expect(viewport.binding_).to.be.instanceof(ViewportBindingNatural_);
    });

    it('should bind to "iOS embed" when iframed', () => {
      win.parent = {};
      const ampDoc = installDocService(win, true).getAmpDoc();
      const viewer = installViewerServiceForDoc(ampDoc);
      sandbox.stub(viewer, 'isEmbedded', () => true);
      const viewport = installViewportServiceForDoc(ampDoc);
      expect(viewport.binding_).to
          .be.instanceof(ViewportBindingNaturalIosEmbed_);
    });

    it('should NOT bind to "iOS embed" when iframed but not embedded', () => {
      win.parent = {};
      const ampDoc = installDocService(win, true).getAmpDoc();
      const viewer = installViewerServiceForDoc(ampDoc);
      sandbox.stub(viewer, 'isEmbedded', () => false);
      const viewport = installViewportServiceForDoc(ampDoc);
      expect(viewport.binding_).to
          .be.instanceof(ViewportBindingNatural_);
    });

    it('should bind to "iOS embed" when not iframed but in dev mode', () => {
      const ampDoc = installDocService(win, true).getAmpDoc();
      getMode(win).development = true;
      const viewer = installViewerServiceForDoc(ampDoc);
      sandbox.stub(viewer, 'isEmbedded', () => false);
      const viewport = installViewportServiceForDoc(ampDoc);
      expect(viewport.binding_).to
          .be.instanceof(ViewportBindingNaturalIosEmbed_);
    });

    it('should bind to "iOS embed" when iframed but in test mode', () => {
      win.parent = {};
      const ampDoc = installDocService(win, true).getAmpDoc();
      getMode(win).test = true;
      const viewer = installViewerServiceForDoc(ampDoc);
      sandbox.stub(viewer, 'isEmbedded', () => false);
      const viewport = installViewportServiceForDoc(ampDoc);
      expect(viewport.binding_).to
          .be.instanceof(ViewportBindingNaturalIosEmbed_);
    });

    it('should NOT bind to "iOS embed" when in dev mode, but iframed', () => {
      win.parent = {};
      const ampDoc = installDocService(win, true).getAmpDoc();
      getMode(win).development = true;
      const viewer = installViewerServiceForDoc(ampDoc);
      sandbox.stub(viewer, 'isEmbedded', () => false);
      const viewport = installViewportServiceForDoc(ampDoc);
      expect(viewport.binding_).to
          .be.instanceof(ViewportBindingNatural_);
    });
  });
});

redpajama_set_name: RedPajamaGithub


text:
► View source for Installations 1000 plus
View source for Installations 1000 plus
← Installations 1000 plus
Note: You are currently viewing documentation for Moodle 2.0. Up-to-date documentation for the latest stable version is available here: Installations 1000 plus.
{{Template:Large Installations}} ==Australia== *Monte Sant' Angelo Mercy College - [http://montenet.monte.nsw.edu.au MonteNet] is used as our colleges student intranet and e-learning portal. Currently running 1.6dev with a few extra custom features thrown in. *Monte Sant' Angelo Mercy College - [http://compass.monte.nsw.edu.au Compass] takes moodle and uses it for a purpose that is a little outside the square. Compass is used as the staff intranet. Each course is refered to as a "Space" and is used by each departmentr to store meating minutes, agendas and teaching documentation. Communications tools such as forums allow to facilitate better teacher communication and the events are used to keep the teachers events online and targeted (e.g. year 9 teachers all know when excursions are on, all english teachers know when reports are due, etc). ==Belgium== *[http://www.ritacollege.be Sint-Ritacollege] We are a school for 12-18 year old students. Currently our database contains about 1600 active users and 130 courses. Always running latest Moodle code since version 1.0.8. ==Croatia (Hrvatska)== *Faculty of Philosophy in Zagreb - [http://omega.ffzg.hr Omega sustav ucenja na daljinu] is used as our faculty-wide e-learning system. Currently running 1.7 stable with more than 3000 users and 180+ courses. ==Czech Republic== *Faculty of Arts, Masaryk University, Brno - [http://www.phil.muni.cz/elf/ ELF], 16,000+ users total, 4,000+ active users in Spring Term 2006, 600+ courses; using Moodle from April 2003. ==Finland== *Tampere Polytechnic or Tampereen ammattikorkeakoulu ([http://www.tamk.fi www.tamk.fi]), is a around 5000 student college or "a university of applied sciences" located at Tampere, Finland. Our moodle installation hosts around 2500 active students. *Lahti Polytechnic or Lahden ammattikorkeakoulu is also a "university of applied sciences", located at Lahti, Finland. We have about 5400 students, and a Moodle installation called [http://reppu.lamk.fi Reppu] (Finnish word for "backpack"). We are approaching the 5000 active users mark. ==Italy== * University of Rome "Roma Tre" - [http://ltaonline.learning.uniroma3.it/ Laboratorio di Tecnologie Audiovisive] hosts two faculties with more than 1600 students. ==Japan== * Kyoto Sangyo University - [http://www.sgu.ac.jp] First university in Japan to completely host all courses in Moodle. * Sapporo Gakuin University - [http://elearning.sgu.ac.jp] About 25 active courses using Moodle. Over 1000 registered users. Now using LDAP, so all 5000 students can login with their standard school ID and password. ==Netherlands== * We are a 5000 people school, one server, 9 locations and using Moodle for almost three years now: https://studiewijzerplus.nl Every schoolyear we start again with counting the number of users and the automatic counter (thanks Penny!) is now pointing at 3103 == Russia == *Moscow State Institute of International Relations (University) - distance education programme [http://www.globalcompetence.ru Management of Global Processes] ==Spain== *[http://www.cicei.com CICEI] - Center of Innovation for the Information Society. We have a production [http://cursos.cicei.com Moodle server] having currently 1,042 users in 72 courses covering a wide range of applications (as of February 6, 2006). ==Switzerland== *[http://moodle.fri-tic.ch/ Centre fri-tic] - Center for ICT Integration of Canton de Fribourg. Currently running 1 moodle for teachers and students from k-12 to High school, with more than 2000 users and about 130 courses. The [http://www.fri-tic.ch/ Centre fri-tic] is also hosting 9 other moodle installations for specific schools. ==United Kingdom== * [http://moodle.altoncollege.ac.uk/ Alton College] - a Beacon sixth form college with over 2,500 active users. * [http://egglescliffe.org.uk/ Egglescliffe School and Sixth Form] - a specialist arts school with over 2,000 active moodlers and 345 courses whose usage was the basis of this [http://education.guardian.co.uk/elearning/story/0,,1583832,00.html Guardian article] * [http://learn.gold.ac.uk Goldsmiths College, London] - over 2,500 active users. See also this [http://www.alt.ac.uk/altc2004/timetable/abstract.php?abstract_id=270 presentation at a 2004 conference]. * [http://www.thanet.ac.uk/ Thanet College] - a "medium sized general FE college", using Moodle as documented in [http://ferl.becta.org.uk/display.cfm?resID=13423 this Ferl case study] * [http://www.westnotts.ac.uk West Nottingahmshire College] - an FE college in the East Midlands. * [http://www.lifeintheuk.org Prepare: Life in the United Kingdom Test] - a free website dedicated to Life in the UK Test. ==United States of America== *[http://www.glyphdoctors.com Glyphdoctors] is a site with over 1200 registered users (as of Feb. 2006) on a shared server host, [http://www.opensourcehost.com Open Source Host]. It will offer online courses in Egyptology and Egyptian hieroglyphs to the general public. Currently, there is one free public course available and a course in Egyptian hieroglyphs under beta testing with a small number of students. *[http://moodle.mcm.edu/ McMurry University]has several Moodle instances, the first begun as a pilot project in June, 2005. McMurry is a small (1400 FTE) undergraduate-only institution affiliated with the United Methodist church. Currently the main instance supports 1700 users (650 active), some 60 instructors, and approximately 180 active courses. * [http://ahshornets.org Aiken High School]has over 2000 registered users in grades 9 - 12 (Feb. 2006). * [http://academies.culver.org/moodle The Culver Academies] has over 1000 registered users in grades 9 - 12. The school has been using Moodle in the classroom since the 2002-2003 school year. Administratively we have a Moodle "course" for faculty-only announcements and materials. * [http://online.warner.edu Warner Southern College] began using Moodle in early 2005 as an experiment to move from a simple webboard system into a full learning management system. The experiment went extremely well and a full transition to Moodle was accomplished in the fall term of 2005. Currently, Warner Southern College has one Moodle instance running with over 1200 registered users (500+ active students, 50+ instructors, 80+ courses and two system administrators) offering associate, bachelor and master of arts degree programs for both traditional site based and full online students. Warner Southern College is a small institution affiliated with the Church of God and accredited by the Southern Association of Colleges and Schools. [[Category:Administrator]] {{Template:Large Installations}}
Template:Large Installations (view source)
Return to Installations 1000 plus.
Retrieved from "https://docs.moodle.org/20/en/Installations_1000_plus"
About MoodleDocs


Below will be the major points about one spot power supply ,From right here you will get the item details including description,feature ,value and some other very best related items ,you can get the specifics that which can be the appropriate to buy and locate the discount cost.
when you may need to study far more testimonials about one spot power supply or relevant product , it's easy to click the image and get extra information regarding the things that you exciting,when you are interested the item ,you may need to read extra critiques.
krell75460 asked I want to hook up a 12 volt strobe light to a battery power supply, independant from my home?
Want to hook up strobe to 12volt power supply, independant of home electrical supply, with a simple momentary push button, to activate.....Can you help me?
I know there are lots of strobe circuits out there. Most run automatically. It doesn't seem like it would be too difficult to put a switch in the right spot for manual triggering. You may have to modify the specs on an existing circuit to make it run on 12VDC. I suggest you go to Google Images and search for "strobe circuit". You'll get many dozens of circuits to look at. Choose one, or several, with the features and complexity you want. Study how they work and then make one and try inserting your manual switch. You'll do some trial and error, but you'll also be learning how to design your own circuit someday.
Tagged amazon web service, business days, list price, power output, power supply, product description, relevant product, sale price, switching power supply. Bookmark the permalink.


text:
Local offices were ready for shutdown
File photo — The William Johnson house would have closed indefinitely had the federal government shutdown.
NATCHEZ — As the clock ticked down Friday, federal employees in the Miss-Lou spent the day in limbo, forced to make plans for a shutdown and what it would mean locally.
Employees at the Natchez National Historical Park and St. Catherine Creek Wildlife Refuge were among those preparing to go home without pay.
Parks Superintendent Kathleen Jenkins said a shutdown would have meant the closure of Melrose and the William Johnson House.
"If there's no appropriation, we can't incur the expense of paying staff or people to be there," Jenkins said.
NPS offices inside the Natchez Visitor Reception Center would be closed, but the visitor's center would stay open, Jenkins said.
Of the approximately 25 employees and a number of volunteers staff members at the local park service, only some security personnel would report to work if the government had shut down, she said.
Jenkins said she had two conference calls with a designated coordinator of the shutdown this week at the NPS regional office in Atlanta.
Jenkins has worked for NPS for 18 years and has experienced potential shutdowns before.
"You don't get used to this, thank goodness, but its part of the process," she said.
Jenkins said preservation work ongoing at Melrose would have halted in the case of a shutdown.
"It's sort of a minefield negotiation of what's allowed and what's not," Jenkins said.
St. Catherine Creek National Wildlife Refuge Manager Bob Strader was preparing Friday to send all but himself and one security officer home.
Bayou Cocodrie near Ferriday and Cat Island in St. Francisville, La., would also have closed, Strader said.
Strader said he was instructed to keep the refuge and its 11 employees updated on the status of a shutdown via the U.S. Department of the Interior website.
"There's instructions on top of instructions," Strader said.
He said cell phones and laptops of employees who would have be furloughed would be locked up at the office during the shutdown.
"The biggest thing for our employees is not knowing; they don't know if they (will) come on work Monday or if they will get paid on days off," Strader said.
"(Employees) are kind of anxious about the potential shut down."
The Vidalia office of the U.S. Army Corps of Engineers was in the same boat Friday.
Public Affairs and communications officer for the Vicksburg District Kavanaugh Breazeale said plans were made in preparation for the shutdown since the Corps is a part of the U.S. Department of Defense.
"We recently had a staff meeting to determine what avenues we would take," he said. "During a shutdown, we would continue to go forward with emergency essential personnel employees only."
Breazeale said these emergency essential employees would consists of positions like lock and damn operators and hydro power plant operators.
Breazeale said the Corps' Vicksburg District — which includes the Vidalia office — employs approximately 1,100 people in the 68,000 square miles over Arkansas, Mississippi and Louisiana, and the government shutdown would have reduced that number greatly.
"Very few of those 1,100 employees are considered essential," he said.
Breazeale said the district was still working out the details of exactly how many employees would be around in the case of a shutdown.
Corps offices all over the district, including the one in Vidalia, would be closed, but emergency employees would be at the offices to provide services, Breazele said.
Natchez Trace Parkway Superintendent Cam Sholly said the Trace would remain open to drivers with slightly scaled down law enforcement patrol during the shutdown.
Picnic areas, campgrounds and the four visitors centers will be closed, Sholly said.
"We will scale back to a degree who is working, but there will still be a satisfactory public safety presence," Sholly said.
Sholly said approximately 130-150 engineers, technical experts, those who process permits and other full-time employees will be furloughed.
The U.S. Postal Service would not be affected by the federal shutdown as far as regular services, said the Jackson post office's Consumer Affairs Manager Doug Kyle.
The postal service would, however, stop taking applications for passports, and today's national U.S. Passport Day was canceled Friday because of the potential of a shutdown, Kyle said.
"Otherwise, everything else is business as usual because the postal service is under a different funding system," Kyle said.
Kyle said the post office operates from postage stamps and other retail sales and fees.
At the U.S. Coast Guard, essential employees that would continue their jobs during the shutdown include search and rescue, port and homeland security law enforcement and environmental response employees, said Lt. J.G. Ryan Gomez, public information officer at the Coast Guard's Vicksburg station.
Gomez said a standard plan for federal shutdowns has been in place since 1980, but that it is consistently updated.
NAACP argues for court to dive in
JACKSON (AP) — Mississippi lawmakers are gone from the Capitol, but arguments about legislative redistricting are continuing with a flurry... read more


Fully certified and ready to take camping Full winter package, king size bed, dual pain windows, slide out bumper with built in bike rack and so much more!!!!! Please call for more info and ask about our long term low rate financing options OAC. We at Rock Island RV are a local and family owned business for over 25 years and look forward to helping you find the RV of your dreams.
2019 Solaire 205-SS. ONLY 23.5 FT OVERALL LENGTH. This Quality built trailer is loaded in and out with options. A few include 1 slide, heated holding tanks for cooler weather camping, RVQ Grill on bumper, Bike Carrier, solid surface counter tops, 8 cu ft fridge, power awning, power tongue jack, gel coat front cap. and so much more please call for more info and ask about our long term low rate financing options OAC. We at Rock Island RV are a local and family owned business for over 25 years and look forward to helping you find the RV of your dreams.