Full Code of emberjs/ember-collection for AI

master 069a3742ca92 cached
95 files
182.2 KB
52.6k tokens
108 symbols
1 requests
Download .txt
Showing preview only (204K chars total). Download the full file or copy to clipboard to get everything.
Repository: emberjs/ember-collection
Branch: master
Commit: 069a3742ca92
Files: 95
Total size: 182.2 KB

Directory structure:
gitextract_ds3n4771/

├── .editorconfig
├── .ember-cli
├── .eslintignore
├── .eslintrc.js
├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .npmignore
├── .nvmrc
├── .template-lintrc.js
├── .watchmanconfig
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── RELEASE.md
├── addon/
│   ├── components/
│   │   ├── ember-collection/
│   │   │   └── template.hbs
│   │   ├── ember-collection.js
│   │   └── ember-native-scrollable.js
│   ├── layouts/
│   │   ├── grid.js
│   │   ├── mixed-grid.js
│   │   └── percentage-columns.js
│   └── utils/
│       ├── identity.js
│       ├── needs-revalidate.js
│       ├── style-generators.js
│       ├── style-properties.js
│       └── translate.js
├── app/
│   ├── components/
│   │   ├── ember-collection.js
│   │   └── ember-native-scrollable.js
│   └── helpers/
│       ├── fixed-grid-layout.js
│       ├── mixed-grid-layout.js
│       └── percentage-columns-layout.js
├── config/
│   ├── ember-try.js
│   └── environment.js
├── ember-cli-build.js
├── index.js
├── package.json
├── testem.js
└── tests/
    ├── acceptance/
    │   └── list-view-test.js
    ├── dummy/
    │   ├── app/
    │   │   ├── app.js
    │   │   ├── components/
    │   │   │   └── .gitkeep
    │   │   ├── controllers/
    │   │   │   ├── .gitkeep
    │   │   │   ├── mixed.js
    │   │   │   ├── percentages.js
    │   │   │   ├── scroll-position.js
    │   │   │   └── simple.js
    │   │   ├── helpers/
    │   │   │   ├── .gitkeep
    │   │   │   └── size-to-style.js
    │   │   ├── index.html
    │   │   ├── models/
    │   │   │   └── .gitkeep
    │   │   ├── resolver.js
    │   │   ├── router.js
    │   │   ├── routes/
    │   │   │   ├── .gitkeep
    │   │   │   ├── mixed.js
    │   │   │   ├── percentages.js
    │   │   │   ├── scroll-position.js
    │   │   │   └── simple.js
    │   │   ├── styles/
    │   │   │   └── app.css
    │   │   ├── templates/
    │   │   │   ├── application.hbs
    │   │   │   ├── components/
    │   │   │   │   └── .gitkeep
    │   │   │   ├── index.hbs
    │   │   │   ├── mixed.hbs
    │   │   │   ├── percentages.hbs
    │   │   │   ├── scroll-position.hbs
    │   │   │   └── simple.hbs
    │   │   └── utils/
    │   │       ├── fixtures.js
    │   │       ├── images.js
    │   │       └── make-model.js
    │   ├── config/
    │   │   ├── ember-cli-update.json
    │   │   ├── environment.js
    │   │   ├── optional-features.json
    │   │   └── targets.js
    │   └── public/
    │       ├── crossdomain.xml
    │       └── robots.txt
    ├── helpers/
    │   ├── destroy-app.js
    │   ├── helpers.js
    │   ├── module-for-acceptance.js
    │   ├── module-for-view.js
    │   └── start-app.js
    ├── index.html
    ├── templates/
    │   ├── fixed-grid.js
    │   ├── indexed.js
    │   └── percentage.js
    ├── test-helper.js
    └── unit/
        ├── .gitkeep
        ├── content-test.js
        ├── fixed-grid-test.js
        ├── layout-test.js
        ├── multi-height-list-view-test.js
        ├── percentage-layout-test.js
        ├── raf-test.js
        ├── recycling-tests.js
        ├── scroll-top-test.js
        ├── starting-index-test.js
        └── total-height-test.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org

root = true


[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 2

[*.hbs]
insert_final_newline = false

[*.{diff,md}]
trim_trailing_whitespace = false


================================================
FILE: .ember-cli
================================================
{
  /**
    Ember CLI sends analytics information by default. The data is completely
    anonymous, but there are times when you might want to disable this behavior.

    Setting `disableAnalytics` to true will prevent any data from being sent.
  */
  "disableAnalytics": false
}


================================================
FILE: .eslintignore
================================================
# unconventional js
/blueprints/*/files/
/vendor/

# compiled output
/dist/
/tmp/

# dependencies
/bower_components/
/node_modules/

# misc
/coverage/
!.*

# ember-try
/.node_modules.ember-try/
/bower.json.ember-try
/package.json.ember-try


================================================
FILE: .eslintrc.js
================================================
module.exports = {
  root: true,
  parser: 'babel-eslint',
  parserOptions: {
    ecmaVersion: 2018,
    sourceType: 'module',
    ecmaFeatures: {
      legacyDecorators: true,
    },
  },
  plugins: [
    'ember'
  ],
  extends: [
    'eslint:recommended',
    'plugin:ember/recommended'
  ],
  env: {
    browser: true
  },
  rules: {
  },
  overrides: [
    // node files
    {
      files: [
        '.eslintrc.js',
        '.template-lintrc.js',
        'ember-cli-build.js',
        'index.js',
        'testem.js',
        'blueprints/*/index.js',
        'config/**/*.js',
        'tests/dummy/config/**/*.js'
      ],
      excludedFiles: [
        'addon/**',
        'addon-test-support/**',
        'app/**',
        'tests/dummy/app/**'
      ],
      parserOptions: {
        sourceType: 'script'
      },
      env: {
        browser: false,
        node: true
      },
      plugins: ['node'],
      rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, {
        // add your custom rules and overrides for node files here
      })
    }
  ]
};


================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on:
  push:
    branches: [master]
  pull_request:
    branches: [master]

concurrency:
  group: ci-${{ github.head_ref || github.ref }}
  cancel-in-progress: true

env:
  NODE_VERSION: 16

jobs:

  lint:
    name: Lint Addon
    runs-on: ubuntu-latest
    
    steps:
      - name: Check out a copy of the repo
        uses: actions/checkout@v3

      - name: Use Node.js ${{ env.NODE_VERSION }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: yarn

      - name: Install dependencies
        run: yarn install --frozen-lockfile

      - name: Lint JS
        run: yarn lint:js
        continue-on-error: true

      - name: Lint HBS
        run: yarn lint:hbs
        continue-on-error: true

  test-addon:
    name: Test Addon

    runs-on: ubuntu-latest

    strategy:
      fail-fast: true
      matrix:
        try-scenario:
          - 'ember-lts-3.28'
          - 'ember-lts-4.8'
          - 'ember-lts-4.12'
          - 'ember-release'

    steps:
    - name: Checkout code
      uses: actions/checkout@v3

    - name: Use Node.js ${{ env.NODE_VERSION }}
      uses: actions/setup-node@v3
      with:
        node-version: ${{ env.NODE_VERSION }}
        cache: yarn

    - name: Install dependencies
      run: yarn install --no-lockfile

    - name: Test
      run: ./node_modules/.bin/ember try:one ${{ matrix.try-scenario }}


================================================
FILE: .gitignore
================================================
# See https://help.github.com/ignore-files/ for more about ignoring files.

# compiled output
/dist/
/tmp/

# dependencies
/bower_components/
/node_modules/

# misc
/.env*
/.pnp*
/.sass-cache
/connect.lock
/coverage/
/libpeerconnection.log
npm-debug.log*
yarn-error.log
testem.log
.DS_Store
test-results.xml

# ember-try
/.node_modules.ember-try/
/bower.json.ember-try
/package.json.ember-try


================================================
FILE: .npmignore
================================================
# compiled output
/dist/
/tmp/

# dependencies
/bower_components/

# misc
/.bowerrc
/.editorconfig
/.ember-cli
/.env*
/.eslintignore
/.eslintrc.js
/.gitignore
/.template-lintrc.js
/.watchmanconfig
/bower.json
/config/ember-try.js
/CONTRIBUTING.md
/ember-cli-build.js
/testem.js
/tests/
/yarn.lock
.gitkeep
/.github/

# ember-try
/.node_modules.ember-try/
/bower.json.ember-try
/package.json.ember-try


================================================
FILE: .nvmrc
================================================
14


================================================
FILE: .template-lintrc.js
================================================
'use strict';

module.exports = {
  extends: 'recommended',
  rules: {
    'no-inline-styles': false,
    'no-quoteless-attributes': false,
    'no-triple-curlies': false,
    'require-button-type': false,
    'style-concatenation': false
  }
};


================================================
FILE: .watchmanconfig
================================================
{
  "ignore_dirs": ["tmp", "dist"]
}


================================================
FILE: CHANGELOG.md
================================================
## v3.0.0 (2023-11-01)

[BREAKING] Require Node 16+

#### :boom: Breaking Change
* [#222](https://github.com/adopted-ember-addons/ember-collection/pull/222) Update to latest layout-bin-packer ([@lukemelia](https://github.com/lukemelia))

#### Committers: 1
- Luke Melia ([@lukemelia](https://github.com/lukemelia))


## v2.0.0 (2023-05-02)

#### :boom: Breaking Change
* [#220](https://github.com/adopted-ember-addons/ember-collection/pull/220) Upgrade to Ember v4.12 ([@mukilane](https://github.com/mukilane))

#### :house: Internal
* [#220](https://github.com/adopted-ember-addons/ember-collection/pull/220) Upgrade to Ember v4.12 ([@mukilane](https://github.com/mukilane))

#### Committers: 1
- Mukil Elango ([@mukilane](https://github.com/mukilane))


## v1.0.0 (2020-06-24)

## v1.0.0-rc.0 (2020-05-25)

#### :boom: Breaking Change
* [#184](https://github.com/adopted-ember-addons/ember-collection/pull/184) Update to 3.12 ([@vasind](https://github.com/vasind))

#### :rocket: Enhancement
* [#182](https://github.com/adopted-ember-addons/ember-collection/pull/182) [CHORE] Update to ember 3.4 and fix tests ([@Gaurav0](https://github.com/Gaurav0))

#### :bug: Bug Fix
* [#180](https://github.com/adopted-ember-addons/ember-collection/pull/180) Check if destroyed before validating, update travis/lint ([@rwwagner90](https://github.com/rwwagner90))

#### :house: Internal
* [#189](https://github.com/adopted-ember-addons/ember-collection/pull/189) [CHORE] Update Travis CI badge link ([@vasind](https://github.com/vasind))
* [#188](https://github.com/adopted-ember-addons/ember-collection/pull/188) chore: Remove ember-cli-deploy and its plugins ([@Alonski](https://github.com/Alonski))
* [#187](https://github.com/adopted-ember-addons/ember-collection/pull/187) [CHORE] release-it setup ([@vasind](https://github.com/vasind))
* [#185](https://github.com/adopted-ember-addons/ember-collection/pull/185) Update addon URL in index.hbs ([@Alonski](https://github.com/Alonski))

#### Committers: 4
- Alon Bukai ([@Alonski](https://github.com/Alonski))
- Gaurav Munjal ([@Gaurav0](https://github.com/Gaurav0))
- Robert Wagner ([@rwwagner90](https://github.com/rwwagner90))
- Vasanth ([@vasind](https://github.com/vasind))

# Change Log

## [1.0.0-alpha.9](https://github.com/emberjs/ember-collection/tree/1.0.0-alpha.9) (2018-08-02)
[Full Changelog](https://github.com/emberjs/ember-collection/compare/v1.0.0-alpha.8...1.0.0-alpha.9)

**Closed issues:**

- Publish alpha 8 [\#171](https://github.com/emberjs/ember-collection/issues/171)
- Publish v1.0.0-alpha.8 to NPM Repository [\#168](https://github.com/emberjs/ember-collection/issues/168)
- didInitAttrs and Ember.k DEPRECATION warnings for ember 2.13.1 [\#150](https://github.com/emberjs/ember-collection/issues/150)

**Merged pull requests:**

- Fix test that fails in recent Firefox & Safari versions under some circumstances [\#174](https://github.com/emberjs/ember-collection/pull/174) ([lukemelia](https://github.com/lukemelia))
- Remove arrayObserver when the component is destroyed [\#173](https://github.com/emberjs/ember-collection/pull/173) ([pieter-v](https://github.com/pieter-v))
- Upgrade `layout-bin-packer` [\#172](https://github.com/emberjs/ember-collection/pull/172) ([alexlafroscia](https://github.com/alexlafroscia))

## [v1.0.0-alpha.8](https://github.com/emberjs/ember-collection/tree/v1.0.0-alpha.8) (2018-01-16)
[Full Changelog](https://github.com/emberjs/ember-collection/compare/v1.0.0-alpha.7...v1.0.0-alpha.8)

**Closed issues:**

- Single js file version [\#165](https://github.com/emberjs/ember-collection/issues/165)
- URL leads to 404 Error [\#147](https://github.com/emberjs/ember-collection/issues/147)
- scroll to item [\#144](https://github.com/emberjs/ember-collection/issues/144)
- Fill gaps in Mixed Grid Layout [\#134](https://github.com/emberjs/ember-collection/issues/134)
- Glimmer 2 Compatibility [\#126](https://github.com/emberjs/ember-collection/issues/126)

**Merged pull requests:**

- Cleanup... [\#166](https://github.com/emberjs/ember-collection/pull/166) ([rwjblue](https://github.com/rwjblue))
- Fix tests broken in browsers that have visible scrollbars [\#164](https://github.com/emberjs/ember-collection/pull/164) ([raytiley](https://github.com/raytiley))
- Clean up Bower dependencies [\#162](https://github.com/emberjs/ember-collection/pull/162) ([Turbo87](https://github.com/Turbo87))
- \[BREAKING\] Bump minimum Node version to 4.5 [\#161](https://github.com/emberjs/ember-collection/pull/161) ([Turbo87](https://github.com/Turbo87))
- Update "ember-cli" to v2.16.2 [\#160](https://github.com/emberjs/ember-collection/pull/160) ([Turbo87](https://github.com/Turbo87))
- Use yarn instead of npm [\#159](https://github.com/emberjs/ember-collection/pull/159) ([Turbo87](https://github.com/Turbo87))
- README: Use TravisCI badge instead of CircleCI [\#158](https://github.com/emberjs/ember-collection/pull/158) ([Turbo87](https://github.com/Turbo87))
- Use TravisCI instead of CircleCI [\#157](https://github.com/emberjs/ember-collection/pull/157) ([Turbo87](https://github.com/Turbo87))
- testem: Run Chrome and Firefox in headless mode [\#156](https://github.com/emberjs/ember-collection/pull/156) ([Turbo87](https://github.com/Turbo87))
- testem: Remove "PhantomJS" target [\#155](https://github.com/emberjs/ember-collection/pull/155) ([Turbo87](https://github.com/Turbo87))
- Bump node to version 6 [\#153](https://github.com/emberjs/ember-collection/pull/153) ([raytiley](https://github.com/raytiley))
- Automated demo deploy via ember-cli-deploy-ghpages [\#151](https://github.com/emberjs/ember-collection/pull/151) ([lolmaus](https://github.com/lolmaus))
- Fix spelling and naming mistake [\#149](https://github.com/emberjs/ember-collection/pull/149) ([Alonski](https://github.com/Alonski))
- Removed ember-try from dependencies [\#148](https://github.com/emberjs/ember-collection/pull/148) ([Alonski](https://github.com/Alonski))
- style prefix fix [\#146](https://github.com/emberjs/ember-collection/pull/146) ([mival](https://github.com/mival))
- Fix readme scroll-right reference [\#139](https://github.com/emberjs/ember-collection/pull/139) ([jubar](https://github.com/jubar))
- Remove deprecated Ember.K [\#136](https://github.com/emberjs/ember-collection/pull/136) ([cibernox](https://github.com/cibernox))
- Prevent `didInitAttrs` deprecation. [\#133](https://github.com/emberjs/ember-collection/pull/133) ([rwjblue](https://github.com/rwjblue))
- Clear cells and cellMap when items changes [\#110](https://github.com/emberjs/ember-collection/pull/110) ([paddyobrien](https://github.com/paddyobrien))

## [v1.0.0-alpha.7](https://github.com/emberjs/ember-collection/tree/v1.0.0-alpha.7) (2016-11-17)
[Full Changelog](https://github.com/emberjs/ember-collection/compare/v1.0.0-alpha.6...v1.0.0-alpha.7)

**Closed issues:**

- Ncaught Error: Assertion Failed: A helper named ‘percentage-columns-layout’ could not be found [\#118](https://github.com/emberjs/ember-collection/issues/118)

**Merged pull requests:**

- Pr/130 [\#131](https://github.com/emberjs/ember-collection/pull/131) ([stefanpenner](https://github.com/stefanpenner))
- Glimmer 2 Compatibility [\#130](https://github.com/emberjs/ember-collection/pull/130) ([paddyobrien](https://github.com/paddyobrien))

## [v1.0.0-alpha.6](https://github.com/emberjs/ember-collection/tree/v1.0.0-alpha.6) (2016-04-25)
[Full Changelog](https://github.com/emberjs/ember-collection/compare/v1.0.0-alpha.5...v1.0.0-alpha.6)

**Closed issues:**

- how to test sort ordered collection when dom elements are not same order as visual order [\#107](https://github.com/emberjs/ember-collection/issues/107)
- A helper named 'percentage-columns-layout' could not be found [\#106](https://github.com/emberjs/ember-collection/issues/106)
- Items not appended using momentum/inertia scroll on iOS [\#105](https://github.com/emberjs/ember-collection/issues/105)
- Could not find addon with name: ember-collection [\#93](https://github.com/emberjs/ember-collection/issues/93)
- Collection cells overlap pop-overs [\#89](https://github.com/emberjs/ember-collection/issues/89)
- fixed-grid.js:74 Uncaught TypeError: height depends on the first argument of visibleWidth\(number\) [\#71](https://github.com/emberjs/ember-collection/issues/71)
- Weird rendering glitch only in safari [\#68](https://github.com/emberjs/ember-collection/issues/68)
- test tooling \(phantomjs support?\) [\#43](https://github.com/emberjs/ember-collection/issues/43)

**Merged pull requests:**

- Update ember-try to 0.2.0. [\#102](https://github.com/emberjs/ember-collection/pull/102) ([rwjblue](https://github.com/rwjblue))
- Update ember-cli from 2.3.0-beta.2 to 2.3.0 [\#101](https://github.com/emberjs/ember-collection/pull/101) ([fpauser](https://github.com/fpauser))
- Update percentage-columns-layout example. [\#95](https://github.com/emberjs/ember-collection/pull/95) ([dustinspecker](https://github.com/dustinspecker))
- Remove template compiler from built assets. [\#91](https://github.com/emberjs/ember-collection/pull/91) ([rwjblue](https://github.com/rwjblue))
- Use Xunit reporter in CI.  [\#90](https://github.com/emberjs/ember-collection/pull/90) ([rwjblue](https://github.com/rwjblue))
- Update releases tested in CI. [\#88](https://github.com/emberjs/ember-collection/pull/88) ([rwjblue](https://github.com/rwjblue))
- Fix percentage-columns-layout [\#87](https://github.com/emberjs/ember-collection/pull/87) ([raytiley](https://github.com/raytiley))
- Update to ember-cli@2.3.0-beta.1. [\#86](https://github.com/emberjs/ember-collection/pull/86) ([rwjblue](https://github.com/rwjblue))
- Update README [\#85](https://github.com/emberjs/ember-collection/pull/85) ([raytiley](https://github.com/raytiley))
- Fallback to setTimeout if requestAnimationFrame is not present [\#84](https://github.com/emberjs/ember-collection/pull/84) ([raytiley](https://github.com/raytiley))
- Fix comparison link in CHANGELOG.md [\#82](https://github.com/emberjs/ember-collection/pull/82) ([tricknotes](https://github.com/tricknotes))
- Delegate to layout for calculating style [\#81](https://github.com/emberjs/ember-collection/pull/81) ([raytiley](https://github.com/raytiley))

## [v1.0.0-alpha.5](https://github.com/emberjs/ember-collection/tree/v1.0.0-alpha.5) (2016-01-21)
[Full Changelog](https://github.com/emberjs/ember-collection/compare/v1.0.0-alpha.4...v1.0.0-alpha.5)

**Closed issues:**

- Deploy dummy app to provide an interactive demo.  [\#73](https://github.com/emberjs/ember-collection/issues/73)
- Is this dead\> [\#70](https://github.com/emberjs/ember-collection/issues/70)
- listening to scroll change  [\#69](https://github.com/emberjs/ember-collection/issues/69)
- Nested data structure support? [\#67](https://github.com/emberjs/ember-collection/issues/67)
- Tests Failing - starting with ember-beta [\#61](https://github.com/emberjs/ember-collection/issues/61)
- Remember scroll position [\#60](https://github.com/emberjs/ember-collection/issues/60)
- use main view as "scrolling container" instead of adding another child view [\#56](https://github.com/emberjs/ember-collection/issues/56)
- ember-collection doesn't support Ember arrays [\#55](https://github.com/emberjs/ember-collection/issues/55)
- Vendoring [\#54](https://github.com/emberjs/ember-collection/issues/54)
- Can't install this component [\#53](https://github.com/emberjs/ember-collection/issues/53)
- Inline style violates Content Security Policy [\#50](https://github.com/emberjs/ember-collection/issues/50)
- Does not work with DS.RecordArray\(\) [\#44](https://github.com/emberjs/ember-collection/issues/44)
- ember dev community slack channel [\#40](https://github.com/emberjs/ember-collection/issues/40)

**Merged pull requests:**

- Prepare for alpha.5 release [\#80](https://github.com/emberjs/ember-collection/pull/80) ([lukemelia](https://github.com/lukemelia))
- Add a note to the scroll-position example in the dummy app about remembering the scroll position across re-renders [\#79](https://github.com/emberjs/ember-collection/pull/79) ([lukemelia](https://github.com/lukemelia))
- Document behavior of scroll-change action in README [\#78](https://github.com/emberjs/ember-collection/pull/78) ([lukemelia](https://github.com/lukemelia))
- Specify the demoURL in the package.json for emberaddons.com and emberobserver.com [\#77](https://github.com/emberjs/ember-collection/pull/77) ([lukemelia](https://github.com/lukemelia))
- Bootstrap styling, removed unused dummy app items [\#76](https://github.com/emberjs/ember-collection/pull/76) ([raytiley](https://github.com/raytiley))
- Adding some context to the online demo [\#75](https://github.com/emberjs/ember-collection/pull/75) ([ef4](https://github.com/ef4))
- automated deploys to github pages [\#74](https://github.com/emberjs/ember-collection/pull/74) ([ef4](https://github.com/ef4))
- Update to use Ember.Array API... [\#66](https://github.com/emberjs/ember-collection/pull/66) ([lukemelia](https://github.com/lukemelia))
- Set Dummy app title more accurately [\#65](https://github.com/emberjs/ember-collection/pull/65) ([lukemelia](https://github.com/lukemelia))
- ember-template-compiler.js appears to be unnecessary for tests, and hardcoding it to released version is problematic for ember-try [\#64](https://github.com/emberjs/ember-collection/pull/64) ([lukemelia](https://github.com/lukemelia))
- It's not necessary to set the box-sizing css property on the ember-native-scrollable [\#63](https://github.com/emberjs/ember-collection/pull/63) ([lukemelia](https://github.com/lukemelia))
- Explain relative positioning, estimated width/height [\#59](https://github.com/emberjs/ember-collection/pull/59) ([samselikoff](https://github.com/samselikoff))
- Update README.md [\#57](https://github.com/emberjs/ember-collection/pull/57) ([DanielOchoa](https://github.com/DanielOchoa))
- Fix typo in build instructions [\#52](https://github.com/emberjs/ember-collection/pull/52) ([aldhsu](https://github.com/aldhsu))
- Use separate variables instead of two-element hashes when it makes sense [\#51](https://github.com/emberjs/ember-collection/pull/51) ([srgpqt](https://github.com/srgpqt))

## [v1.0.0-alpha.4](https://github.com/emberjs/ember-collection/tree/v1.0.0-alpha.4) (2015-09-15)
[Full Changelog](https://github.com/emberjs/ember-collection/compare/v1.0.0-alpha.3...v1.0.0-alpha.4)

## [v1.0.0-alpha.3](https://github.com/emberjs/ember-collection/tree/v1.0.0-alpha.3) (2015-09-15)
[Full Changelog](https://github.com/emberjs/ember-collection/compare/v1.0.0-alpha.2...v1.0.0-alpha.3)

**Merged pull requests:**

- If scrollChange action is provided, emit scroll changes to it and bind the scroll position [\#48](https://github.com/emberjs/ember-collection/pull/48) ([lukemelia](https://github.com/lukemelia))

## [v1.0.0-alpha.2](https://github.com/emberjs/ember-collection/tree/v1.0.0-alpha.2) (2015-09-14)
[Full Changelog](https://github.com/emberjs/ember-collection/compare/v1.0.0-alpha.1...v1.0.0-alpha.2)

## [v1.0.0-alpha.1](https://github.com/emberjs/ember-collection/tree/v1.0.0-alpha.1) (2015-09-14)
**Fixed bugs:**

- Some tests fail in Safari [\#19](https://github.com/emberjs/ember-collection/issues/19)
- Memory Leak [\#14](https://github.com/emberjs/ember-collection/issues/14)
- requestAnimationFrame cycle runs forever [\#12](https://github.com/emberjs/ember-collection/issues/12)

**Closed issues:**

- build [\#45](https://github.com/emberjs/ember-collection/issues/45)
- Make a virtual scrolling implementation [\#36](https://github.com/emberjs/ember-collection/issues/36)
- build failure [\#33](https://github.com/emberjs/ember-collection/issues/33)

**Merged pull requests:**

- Cleanup translate style [\#42](https://github.com/emberjs/ember-collection/pull/42) ([krisselden](https://github.com/krisselden))
- Use Ember.set to set \_clientSize, so it is observable. [\#41](https://github.com/emberjs/ember-collection/pull/41) ([lukemelia](https://github.com/lukemelia))
- Abstract scrolling [\#35](https://github.com/emberjs/ember-collection/pull/35) ([krisselden](https://github.com/krisselden))
- Abstract scrolling rebased [\#34](https://github.com/emberjs/ember-collection/pull/34) ([lukemelia](https://github.com/lukemelia))
- disabling prototype extentions [\#31](https://github.com/emberjs/ember-collection/pull/31) ([shaunc](https://github.com/shaunc))
- Assorted clean-up [\#29](https://github.com/emberjs/ember-collection/pull/29) ([jonnii](https://github.com/jonnii))
- Remove list item class [\#28](https://github.com/emberjs/ember-collection/pull/28) ([jonnii](https://github.com/jonnii))
- Buffer fix [\#25](https://github.com/emberjs/ember-collection/pull/25) ([shaunc](https://github.com/shaunc))
- fixes scroll top bugs \(including in safari\) [\#24](https://github.com/emberjs/ember-collection/pull/24) ([shaunc](https://github.com/shaunc))
- Remove class from collection container [\#23](https://github.com/emberjs/ember-collection/pull/23) ([jonnii](https://github.com/jonnii))
- Move container class to tests [\#22](https://github.com/emberjs/ember-collection/pull/22) ([jonnii](https://github.com/jonnii))
- Remove some uneeded tests [\#20](https://github.com/emberjs/ember-collection/pull/20) ([mmun](https://github.com/mmun))
- activates more of the tests; adds "display in fixed grid" test to dem… [\#18](https://github.com/emberjs/ember-collection/pull/18) ([mmun](https://github.com/mmun))
- Cancel animation frame when destroying component [\#16](https://github.com/emberjs/ember-collection/pull/16) ([raytiley](https://github.com/raytiley))
- Pass along height to layout-bin-packer in mixed grid [\#15](https://github.com/emberjs/ember-collection/pull/15) ([raytiley](https://github.com/raytiley))
- Fix dummy app [\#13](https://github.com/emberjs/ember-collection/pull/13) ([raytiley](https://github.com/raytiley))
- First pass at updating readme [\#11](https://github.com/emberjs/ember-collection/pull/11) ([raytiley](https://github.com/raytiley))
- Cleanup [\#9](https://github.com/emberjs/ember-collection/pull/9) ([mmun](https://github.com/mmun))
- Fix dummy app [\#8](https://github.com/emberjs/ember-collection/pull/8) ([mmun](https://github.com/mmun))
- Use ember@1.13.8 as default ember [\#7](https://github.com/emberjs/ember-collection/pull/7) ([mmun](https://github.com/mmun))
- rename to ember-collection [\#4](https://github.com/emberjs/ember-collection/pull/4) ([jonnii](https://github.com/jonnii))
- Hook up CircleCI [\#2](https://github.com/emberjs/ember-collection/pull/2) ([mmun](https://github.com/mmun))
- Tests 2.0 [\#1](https://github.com/emberjs/ember-collection/pull/1) ([shaunc](https://github.com/shaunc))



\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*

================================================
FILE: CODE_OF_CONDUCT.md
================================================
The Ember team and community are committed to everyone having a safe and inclusive experience.

**Our Community Guidelines / Code of Conduct can be found here**:

http://emberjs.com/guidelines/

For a history of updates, see the page history here:

https://github.com/emberjs/website/commits/master/source/guidelines.html.erb


================================================
FILE: CONTRIBUTING.md
================================================
# How To Contribute

## Installation

* `git clone <repository-url>`
* `cd my-addon`
* `npm install`

## Linting

* `npm run lint:hbs`
* `npm run lint:js`
* `npm run lint:js -- --fix`

## Running tests

* `ember test` – Runs the test suite on the current Ember version
* `ember test --server` – Runs the test suite in "watch mode"
* `ember try:each` – Runs the test suite against multiple Ember versions

## Running the dummy application

* `ember serve`
* Visit the dummy application at [http://localhost:4200](http://localhost:4200).

For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/).

================================================
FILE: LICENSE.md
================================================
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Ember Collection

[![Github Actions](https://github.com/adopted-ember-addons/ember-collection/workflows/CI/badge.svg)](https://github.com/adopted-ember-addons/ember-collection/actions?query=workflow%3ACI)

[![Ember Observer Score](http://emberobserver.com/badges/ember-collection.svg)](http://emberobserver.com/addons/ember-collection)

An efficient incremental rendering component with support for custom layouts and large lists.

### Table of Contents

1. [Installation](#installation)
1. [Usage](#usage)
1. [Layouts](#layouts)
1. [Build it](#build-it)
1. [How it works](#how-it-works)
1. [Run unit tests](#running-unit-tests)

## Installation
  
  * `ember install ember-collection`

## Submitting bugs

Create a reproduction of the bug in https://ember-twiddle.com/

It would help us greatly to help you and to improve ember collection.

## Usage

The height of the collection is inferred from its nearest relative parent.
This is so you can just use CSS to style the container.

So, first make sure the collection has a parent with `position: relative`, and
then render a template:

```handlebars
{{#ember-collection
  items=model
  cell-layout=(fixed-grid-layout 800 50) as |item index|
}}
  {{item.name}}
{{/ember-collection}}
```

Next, let's feed our template with some data:

``` javascript
// define index route and return some data from model
export default Ember.Route.extend({
  model: function() {
    var items = [];
    for (var i = 0; i < 10000; i++) {
      items.push({name: "Item " + i});
    }
    return items;
  }
});
```

Shazam! You should be able to see a scrollable area with 10,000 items in it.

### Required parameters

You must specify `cell-layout` parameter so that *EmberCollection* can layout out your items. The provided layouts are described in the [Layouts](#layouts) section.

### Estimating width/height

You can pass `estimated-width` and `estimated-height` to the collection, for situations where the collection cannot infer its height from its parent (e.g., when there's no DOM in FastBoot).

Once the collection has been rendered, `estimated-width` and `estimated-height` have no effect.


### Actions

#### scroll-change

If you do not provide a `scroll-change` action name or closure action, scrolling will work normally.

If you *do* specify `scroll-change`, ember-collection assumes that you want to handle the scroll-change action in a true data down, actions up manner. For this reason, ember-collection will not set `scroll-left` and `scroll-top` itself, but rather rely on you to update those properties based on action handling as you see fit.

An example of specifying an action and keeping scrolling working normally looks like this:

```hbs
{{#ember-collection items=model cell-layout=(fixed-grid-layout itemWidth itemHeight)
    scroll-left=scrollLeft scroll-top=scrollTop scroll-change=(action "scrollChange")
    as |item index| }}
  <div class="list-item">{{item.name}}</div>
{{/ember-collection}}
```

```js
export default Ember.Controller.extend({
  scrollLeft: 0,
  scrollTop: 0,
  actions: {
    scrollChange(scrollLeft, scrollTop) {
      this.set('scrollLeft', scrollLeft);
      this.set('scrollTop', scrollTop);
    }
  }
});
```

## Layouts

### Fixed Grid Layout

The `fixed-grid-layout` will arrange the items in a grid to to fill the content area. The arguments for the layout are:

| Argument     | Description                 |
| ------------ | --------------------------- |
| `itemWidth`  | The width of each item      |
| `itemHeight` | The height of each item     |

```hbs
{{#ember-collection items=model cell-layout=(fixed-grid-layout itemWidth itemHeight)
    scroll-left=scrollLeft scroll-top=scrollTop scroll-change=(action "scrollChange")
    as |item index| }}
  <div class="list-item">{{item.name}}</div>
{{/ember-collection}}
```

### Mixed Grid Layout

The `mixed-grid-layout` is used when each item has a known `width` and `height` and will arrange the items in rows from left to right fitting as many items in each row as possible. The arguments for the layout are:

| Argument    | Description                 |
| ----------- | --------------------------- |
| `itemSizes` | A collection of objects having `width` and `height` properties. Used to lookup with size of the corresponding index in the collection.  |

For example if you want the first element in `items` to have a size of `20x50` then the first element in `itemSizes` must be `{width: 20, height: 50}`. If the items have `width` and `height` properties you can use pass collection to `items` and `itemSizes`. 

```hbs
{{#ember-collection items=model cell-layout=(mixed-grid-layout itemSizes)
    scroll-left=scrollLeft scroll-top=scrollTop scroll-change=(action "scrollChange")
    as |item index| }}
  <div class="list-item">{{item.name}}</div>
{{/ember-collection}}
```

### Percentage Columns Layout

The `percentage-columns-layout` allows items to be laid out in a fixed number of columns sized using percentage widths with a fixed height in pixels. The arguments for the layout are:

| Argument     | Description                                                                                                    |
| ------------ | -------------------------------------------------------------------------------------------------------------- |
| `itemCount`      | The number of items passed to the collection. This is usually the number of items in the model (`model.length`).       |
| `columns`    | An array of numbers not totaling more than 100. e.g. `[33.333, 66.666]`, `[25, 50, 10, 15]`                    |
| `itemHeight` | The height in pixels of each item.                                                                             |

```hbs
{{#ember-collection items=model cell-layout=(percentage-columns-layout itemCount columns itemHeight)
    scroll-left=scrollLeft scroll-top=scrollTop scroll-change=(action "scrollChange")
    as |item index| }}
  <div class="list-item">{{item.name}}</div>
{{/ember-collection}}
```

### Creating your own layout

If none of the built in layouts included with *EmberCollection* fit your needs you can create your own. A layout is simply an object returned from a helper that conforms to the following interface.

```js
import Ember from 'ember'

export default Ember.Helper.helper(function(params, hash) {
   return {
    /**
     * Return an object that describes the size of the content area
     */
    contentSize(clientWidth, clientHeight) {
        return { width, height };
    }
    
    /**
     * Return the index of the first item shown.
     */
    indexAt(offsetX, offsetY, clientWidth, clientHeight) {
        return Number;
    }
    
    /**
     *  Return the number of items to display
     */
    count(offsetX, offsetY, width, height) {
        return Number;
    }
    
    /**
     * Return the css that should be used to set the size and position of the item.
     */
    formatItemStyle(itemIndex, clientWidth, clientHeight) {
        return String;
    }
  } 
});
```

## Build It

1. `git clone https://github.com/adopted-ember-addons/ember-collection.git`
2. `cd ember-collection`
3. `npm install`
5. `ember build`

## How it works

*EmberCollection* will create enough rows to fill the visible area. It reacts to scroll events and reuses/repositions the rows as scrolled.

## Running unit tests

```sh
npm install
npm test
```

## Thanks

A lot of the work was sponsored by [Yapp Labs](https://www.yapp.us/), and some work was sponsored by [Tightrope Media Systems](http://trms.com).


================================================
FILE: RELEASE.md
================================================
# Release

Releases are mostly automated using
[release-it](https://github.com/release-it/release-it/) and
[lerna-changelog](https://github.com/lerna/lerna-changelog/).


## Preparation

Since the majority of the actual release process is automated, the primary
remaining task prior to releasing is confirming that all pull requests that
have been merged since the last release have been labeled with the appropriate
`lerna-changelog` labels and the titles have been updated to ensure they
represent something that would make sense to our users. Some great information
on why this is important can be found at
[keepachangelog.com](https://keepachangelog.com/en/1.0.0/), but the overall
guiding principle here is that changelogs are for humans, not machines.

When reviewing merged PR's the labels to be used are:

* breaking - Used when the PR is considered a breaking change.
* enhancement - Used when the PR adds a new feature or enhancement.
* bug - Used when the PR fixes a bug included in a previous release.
* documentation - Used when the PR adds or updates documentation.
* internal - Used for internal changes that still require a mention in the
  changelog/release notes.


## Release

Once the prep work is completed, the actual release is straight forward:

* First ensure that you have `release-it` installed globally, generally done by
  using one of the following commands:

```
# using https://volta.sh
volta install release-it

# using Yarn
yarn global add release-it

# using npm
npm install --global release-it
```

* Second, ensure that you have installed your projects dependencies:

```
yarn install
```

* And last (but not least 😁) do your release. It requires a
  [GitHub personal access token](https://github.com/settings/tokens) as
  `$GITHUB_AUTH` environment variable. Only "repo" access is needed; no "admin"
  or other scopes are required.

```
export GITHUB_AUTH="f941e0..."
release-it
```

[release-it](https://github.com/release-it/release-it/) manages the actual
release process. It will prompt you to to choose the version number after which
you will have the chance to hand tweak the changelog to be used (for the
`CHANGELOG.md` and GitHub release), then `release-it` continues on to tagging,
pushing the tag and commits, etc.


================================================
FILE: addon/components/ember-collection/template.hbs
================================================
<EmberNativeScrollable @content-size={{this._contentSize}} @scroll-left={{this._scrollLeft}} @scroll-top={{this._scrollTop}} @scrollChange={{this.scrollChange}} @clientSizeChange={{this.clientSizeChange}}>
  <div>
    {{~#each this._cells as |cell|~}}
      <div style={{{cell.style}}}>{{yield cell.item cell.index }}</div>
    {{~/each~}}
  </div>
</EmberNativeScrollable>


================================================
FILE: addon/components/ember-collection.js
================================================
import { A } from '@ember/array';
import Component from '@ember/component';
import { action, set, get } from '@ember/object';
import layout from './ember-collection/template';
import identity from '../utils/identity';
import needsRevalidate from '../utils/needs-revalidate';

class Cell {
  constructor(key, item, index, style) {
    this.key = key;
    this.hidden = false;
    this.item = item;
    this.index = index;
    this.style = style;
  }
}

function noop() {}

export default Component.extend({
  layout: layout,

  init() {
    // State pulled from attrs is prefixed with an underscore
    // so that there's no chance of shadowing the attrs proxy.
    this._buffer = undefined;
    this._cellLayout = undefined;
    this._rawItems = undefined;
    this._items = undefined;
    this._scrollLeft = undefined;
    this._scrollTop = undefined;
    this._clientWidth = undefined;
    this._clientHeight = undefined;
    this._contentSize = undefined;

    // this.firstCell = undefined;
    // this.lastCell = undefined;
    // this.cellCount = undefined;
    this.contentElement = undefined;
    this._cells = A();
    this._cellMap = Object.create(null);

    // TODO: Super calls should always be at the top of the constructor.
    // I had to move the super call after the properties were defined to
    // work around what I believe is a bug in the attrs proxy. The problem
    // seems to arise when you:
    //
    //   1. Call this._super() immediately.
    //   2. Set a property on `this` that is both not in the
    //      initial attrs hash and not on the prototype.
    this._super();

    // initialize from passed in attrs
    let buffer = this.getAttr('buffer'); // getIntAttr('buffer', 5)
    this._buffer = (typeof buffer === 'number') ? buffer : 5;
    this._scrollLeft = this.getAttr('scroll-left') | 0;
    this._scrollTop = this.getAttr('scroll-top') | 0;
    this._clientWidth = this.getAttr('estimated-width') | 0;
    this._clientHeight = this.getAttr('estimated-height') | 0;
    this._scrollChange = this.getAttr('scroll-change');
  },

  _needsRevalidate(){
    if (this.isDestroyed || this.isDestroying) {return;}
    if (this._isGlimmer2()) {
      this.rerender();
    } else {
      needsRevalidate(this);
    }
  },

  didReceiveAttrs() {
    // Work around emberjs/ember.js#11992. Affects <=1.13.8 and <=2.0.0.
    // This will likely be patched in 1.13.9 and 2.0.1.
    this._super();

    this.updateItems();
    this.updateScrollPosition();
  },

  willDestroyElement() {
    if (this._items && this._items.removeArrayObserver) {
      this._items.removeArrayObserver(this, {
        willChange: noop,
        didChange: '_needsRevalidate'
      });
    }
  },

  updateItems(){
    this._cellLayout = this.getAttr('cell-layout');
    var rawItems = this.getAttr('items');

    if (this._rawItems !== rawItems) {
      if (this._items && this._items.removeArrayObserver) {
        this._items.removeArrayObserver(this, {
          willChange: noop,
          didChange: '_needsRevalidate'
        });
      }
      this._rawItems = rawItems;
      var items = A(rawItems);
      this.set('_items', items);

      if (items && items.addArrayObserver) {
        items.addArrayObserver(this, {
          willChange: noop,
          didChange: '_needsRevalidate'
        });
      }
    }
  },

  updateScrollPosition(){
    if (!this._scrollChange) { return; } // don't process bound scroll coords unless our action is being handled
    let scrollLeftAttr = this.getAttr('scroll-left');
    if (scrollLeftAttr !== undefined) {
      scrollLeftAttr = parseInt(scrollLeftAttr, 10);
      if (this._scrollLeft !== scrollLeftAttr) {
        this.set('_scrollLeft', scrollLeftAttr);
      }
    }

    let scrollTopAttr = this.getAttr('scroll-top');
    if (scrollTopAttr !== undefined) {
      scrollTopAttr = parseInt(scrollTopAttr, 10);
      if (this._scrollTop !== scrollTopAttr) {
        // console.log('updateScrollPosition', this._scrollTop, scrollTopAttr);
        this.set('_scrollTop', scrollTopAttr);
      }
    }
  },

  updateContentSize() {
    var cellLayout = this._cellLayout;
    var contentSize = cellLayout.contentSize(this._clientWidth, this._clientHeight);
    if (this._contentSize === undefined ||
        contentSize.width !== this._contentSize.width ||
        contentSize.height !== this._contentSize.height) {
      this.set('_contentSize', contentSize);
    }
  },

  willRender: function() {
    this.updateCells();
    this.updateContentSize();
  },

  updateCells() {
    if (!this._items) { return; }
    const numItems = get(this._items, 'length');
    if (this._cellLayout.length !== numItems) {
      this._cellLayout.length = numItems;
    }

    var priorMap = this._cellMap;
    var cellMap = Object.create(null);

    var index = this._cellLayout.indexAt(this._scrollLeft, this._scrollTop, this._clientWidth, this._clientHeight);
    var count = this._cellLayout.count(this._scrollLeft, this._scrollTop, this._clientWidth, this._clientHeight);
    var items = this._items;
    var bufferBefore = Math.min(index, this._buffer);
    index -= bufferBefore;
    count += bufferBefore;
    count = Math.min(count + this._buffer, get(items, 'length') - index);
    var i, style, itemIndex, itemKey, cell;

    var newItems = [];

    for (i=0; i<count; i++) {
      itemIndex = index+i;
      itemKey = identity(items.objectAt(itemIndex));
      if (priorMap) {
        cell = priorMap[itemKey];
      }
      if (cell) {
        style = this._cellLayout.formatItemStyle(itemIndex, this._clientWidth, this._clientHeight);
        set(cell, 'style', style);
        set(cell, 'hidden', false);
        set(cell, 'key', itemKey);
        set(cell, 'index', itemIndex);
        cellMap[itemKey] = cell;
      } else {
        newItems.push(itemIndex);
      }
    }

    for (i=0; i<this._cells.length; i++) {
      cell = this._cells[i];
      if (!cellMap[cell.key]) {
        if (newItems.length) {
          itemIndex = newItems.pop();
          let item = items.objectAt(itemIndex);
          itemKey = identity(item);
          style = this._cellLayout.formatItemStyle(itemIndex, this._clientWidth, this._clientHeight);
          set(cell, 'style', style);
          set(cell, 'key', itemKey);
          set(cell, 'index', itemIndex);
          set(cell, 'item', item);
          set(cell, 'hidden', false);
          cellMap[itemKey] = cell;
        } else {
          set(cell, 'hidden', true);
          set(cell, 'style', 'height: 0; display: none;');
        }
      }
    }

    for (i=0; i<newItems.length; i++) {
      itemIndex = newItems[i];
      let item = items.objectAt(itemIndex);
      itemKey = identity(item);
      style = this._cellLayout.formatItemStyle(itemIndex, this._clientWidth, this._clientHeight);
      cell = new Cell(itemKey, item, itemIndex, style);
      cellMap[itemKey] = cell;
      this._cells.pushObject(cell);
    }
    this._cellMap = cellMap;
  },

  _isGlimmer2() {
    return !this._renderNode;
  },

  @action
  scrollChange(scrollLeft, scrollTop) {
    if (this._scrollChange) {
      this._scrollChange(scrollLeft, scrollTop);
    } else {
      if (scrollLeft !== this._scrollLeft ||
          scrollTop !== this._scrollTop) {
        set(this, '_scrollLeft', scrollLeft);
        set(this, '_scrollTop', scrollTop);
        this._needsRevalidate();
      }
    }
  },

  @action
  clientSizeChange(clientWidth, clientHeight) {
    if (this._clientWidth !== clientWidth ||
        this._clientHeight !== clientHeight) {
      set(this, '_clientWidth', clientWidth);
      set(this, '_clientHeight', clientHeight);
      this._needsRevalidate();
    }
  }
});


================================================
FILE: addon/components/ember-native-scrollable.js
================================================
import { join } from '@ember/runloop';
import Component from '@ember/component';
import { translate } from 'ember-collection/utils/translate';
import { styleProperty } from 'ember-collection/utils/style-properties';

const overflowScrollingProp = styleProperty('overflowScrolling');

export default Component.extend({
  init() {
    this._clientWidth = 0;
    this._clientHeight = 0;
    this._scrollLeft = 0;
    this._scrollTop = 0;
    this._animationFrame = undefined;
    this._super();
  },
  didReceiveAttrs() {
    this._contentSize = this.getAttr('content-size');
    this._scrollLeft = this.getAttr('scroll-left');
    this._scrollTop = this.getAttr('scroll-top');
  },
  didInsertElement() {
    this.contentElement = this.element.firstElementChild;
    this.applyStyle();
    this.applyContentSize();
    this.syncScrollFromAttr();
    this.startScrollCheck();
  },
  didUpdate() {
    this.applyContentSize();
    this.syncScrollFromAttr();
  },
  willDestroyElement() {
    this.cancelScrollCheck();
    this.contentElement = undefined;
  },
  applyStyle() {
    if (overflowScrollingProp) {
      this.element.style.overflow = 'scroll';
      this.element.style[overflowScrollingProp] = 'touch';
    } else {
      this.element.style.overflow = 'auto';
    }

    // hack to force render buffer so outside doesn't repaint on scroll
    translate(this.element, 0, 0);

    this.element.style.position = 'absolute';
    this.element.style.left = 0;
    this.element.style.top = 0;
    this.element.style.bottom = 0;
    this.element.style.right = 0;
  },
  applyContentSize() {
    this.contentElement.style.position = 'relative';
    this.contentElement.style.width = this._contentSize.width + 'px';
    this.contentElement.style.height = this._contentSize.height + 'px';
  },
  syncScrollFromAttr() {
    if (this._appliedScrollTop !== this._scrollTop) {
      this._appliedScrollTop = this._scrollTop;
      if (this._scrollTop >= 0) {
        this.element.scrollTop = this._scrollTop;
      }
    }
    if (this._appliedScrollLeft !== this._scrollLeft) {
      this._appliedScrollLeft = this._scrollLeft;
      if (this._scrollLeft >= 0) {
        this.element.scrollLeft = this._scrollLeft;
      }
    }
  },
  startScrollCheck() {
    const component = this;
    function step() {
      component.scrollCheck();
      nextStep();
    }
    function nextStep() {
      if (window.requestAnimationFrame) {
        component._animationFrame = requestAnimationFrame(step);
      } else {
        component._animationFrame = setTimeout(step, 16);
      }
    }
    nextStep();
  },
  cancelScrollCheck() {
    if (this._animationFrame) {
      if (window.requestAnimationFrame) {
        cancelAnimationFrame(this._animationFrame);
      } else {
        clearTimeout(this._animationFrame);
      }
      this._animationFrame = undefined;
    }
  },
  scrollCheck() {
    let element = this.element;
    let scrollLeft = element.scrollLeft;
    let scrollTop = element.scrollTop;
    let scrollChanged = false;
    if (scrollLeft !== this._appliedScrollLeft || scrollTop !== this._appliedScrollTop) {
      scrollChanged = true;
      this._appliedScrollLeft = scrollLeft;
      this._appliedScrollTop = scrollTop;
    }

    let clientWidth = element.clientWidth;
    let clientHeight = element.clientHeight;
    let clientSizeChanged = false;
    if (clientWidth !== this._clientWidth || clientHeight !== this._clientHeight) {
      clientSizeChanged = true;
      this._clientWidth = clientWidth;
      this._clientHeight = clientHeight;
    }

    if (scrollChanged || clientSizeChanged) {
      join(this, function sendActionsFromScrollCheck(){
        if (scrollChanged) {
          this.scrollChange(scrollLeft, scrollTop);
        }
        if (clientSizeChanged) {
          this.clientSizeChange(clientWidth, clientHeight);
        }
      });
    }
  }
});


================================================
FILE: addon/layouts/grid.js
================================================
import FixedGrid from 'layout-bin-packer/fixed-grid';
import { formatPixelStyle } from '../utils/style-generators';

export default class Grid
{
  constructor(cellWidth, cellHeight) {
    this.length = 0;
    this.bin = new FixedGrid(this, cellWidth, cellHeight);
  }

  contentSize(clientWidth/*, clientHeight*/) {
    return {
      width: clientWidth,
      height: this.bin.height(clientWidth)
    };
  }

  indexAt(offsetX, offsetY, width, height) {
    return this.bin.visibleStartingIndex(offsetY, width, height);
  }

  positionAt(index, width /*,height*/) {
    return this.bin.position(index, width);
  }

  widthAt(index) {
    return this.bin.widthAtIndex(index);
  }

  heightAt(index) {
    return this.bin.heightAtIndex(index);
  }

  count(offsetX, offsetY, width, height) {
    return this.bin.numberVisibleWithin(offsetY, width, height, true);
  }

  formatItemStyle(itemIndex, clientWidth, clientHeight) {
    let pos = this.positionAt(itemIndex, clientWidth, clientHeight);
    let width = this.widthAt(itemIndex, clientWidth, clientHeight);
    let height = this.heightAt(itemIndex, clientWidth, clientHeight);
    return formatPixelStyle(pos, width, height);
  }
}


================================================
FILE: addon/layouts/mixed-grid.js
================================================
import ShelfFirst from 'layout-bin-packer/shelf-first';
import { formatPixelStyle } from '../utils/style-generators';

export default class MixedGrid
{
  constructor(content, width) {
    this.content = content;
    this.bin = new ShelfFirst(content, width);
  }

  contentSize(clientWidth/*, clientHeight*/) {
    return {
      width: clientWidth,
      height: this.bin.height(clientWidth)
    };
  }

  indexAt(offsetX, offsetY, width, height) {
    return this.bin.visibleStartingIndex(offsetY, width, height);
  }

  positionAt(index, width, height) {
    return this.bin.position(index, width, height);
  }

  widthAt(index) {
    return this.bin.widthAtIndex(index);
  }

  heightAt(index) {
    return this.bin.heightAtIndex(index);
  }

  count(offsetX, offsetY, width, height) {
    return this.bin.numberVisibleWithin(offsetY, width, height, true);
  }
  
  formatItemStyle(itemIndex, clientWidth, clientHeight) {
    let pos = this.positionAt(itemIndex, clientWidth, clientHeight);
    let width = this.widthAt(itemIndex, clientWidth, clientHeight);
    let height = this.heightAt(itemIndex, clientWidth, clientHeight);
    return formatPixelStyle(pos, width, height);
  }
}


================================================
FILE: addon/layouts/percentage-columns.js
================================================
import { assert } from '@ember/debug';
import ShelfFirst from 'layout-bin-packer/shelf-first';
import { formatPercentageStyle } from '../utils/style-generators';

export default class PercentageColumns
{
  // How this layout works is by creating a fake grid that is 100px wide.
  // Each item's width is set to be the size of the column. The ShelfFirst lays out everything according to this fake grid.
  // When ember-collection asks for the style in formatItemStyle we pull the percent property to use as the width.
  constructor(itemCount, columns, height) {
    let total = columns.reduce(function(a, b) {
        return a+b;
    });
    // Assert that the columns add up to 100. We don't want to enforce that they are EXACTLY 100 in case the user wants to use percentages.
    // for example [33.333, 66.666]
    assert('All columns must total 100 ' + total, total > 99 && total <= 100 );
    let positions = [];
    var ci = 0;
    for (var i = 0; i < itemCount; i++) {
        positions.push({
            width: columns[ci],
            height: height,
            percent: columns[ci]
        });
        
        ci++;
        
        if (ci >= columns.length) {
            ci = 0;
        }
    }
    this.positions = positions;
    this.bin = new ShelfFirst(positions, 100);
  }

  contentSize(clientWidth/*, clientHeight*/) {
    let size = {
      width: clientWidth,
      height: this.bin.height(100)
    };
    return size;
  }

  indexAt(offsetX, offsetY, width, height) {
    return this.bin.visibleStartingIndex(offsetY, 100, height);
  }

  positionAt(index, width, height) {
    return this.bin.position(index, 100, height);
  }

  widthAt(index) {
    return this.bin.widthAtIndex(index);
  }

  heightAt(index) {
    return this.bin.heightAtIndex(index);
  }

  count(offsetX, offsetY, width, height) {
    return this.bin.numberVisibleWithin(offsetY, 100, height, true);
  }
 
  formatItemStyle(itemIndex, clientWidth, clientHeight) {
    let pos = this.positionAt(itemIndex, 100, clientHeight);
    let width = this.positions[itemIndex].percent;
    let height = this.heightAt(itemIndex, 100, clientHeight);
    let x = Math.floor((pos.x / 100) * clientWidth);
    return formatPercentageStyle({x:x, y:pos.y}, width, height);
  }
}


================================================
FILE: addon/utils/identity.js
================================================
import { guidFor } from '@ember/object/internals';

export default function identity(item) {
  let key;
  let type = typeof item;

  if (type === 'string' || type === 'number') {
    key = item;
  } else {
    key = guidFor(item);
  }

  return key;
}


================================================
FILE: addon/utils/needs-revalidate.js
================================================
export default function needsRevalidate(view){
  view._renderNode.isDirty = true;
  view._renderNode.ownerNode.emberView.scheduleRevalidate(view._renderNode, view.toString(), 'rerendering via needsRevalidate');
}


================================================
FILE: addon/utils/style-generators.js
================================================
import { translateCSS } from './translate';

export function formatPixelStyle(pos, width, height) {
  let css = 'position:absolute;top:0;left:0;';
  css += translateCSS(pos.x, pos.y);
  css += 'width:' + width + 'px;height:' + height + 'px;';
  return css;
}

export function formatPercentageStyle(pos, width, height) {
  let css = 'position:absolute;top:0;left:0;';
  css += translateCSS(pos.x, pos.y);
  css += 'width:' + width + '%;height:' + height + 'px;';
  return css;
}

================================================
FILE: addon/utils/style-properties.js
================================================
import { capitalize, camelize } from '@ember/string';
const stylePrefixes  = ['webkit', 'Webkit',  'ms',  'Moz',  'O'];
const cssPrefixes    = ['-webkit-','-ms-','-moz-','-o-'];

const style = typeof document !== 'undefined' && document.documentElement && document.documentElement.style;

function findProperty(property, css) {
  let prop = css ? camelize(property) : property;
  if (prop in style) {
    return property;
  }
  let capitalized = capitalize(prop);
  for (let i=0; i<stylePrefixes.length; i++) {
    let prefixed = stylePrefixes[i] + capitalized;
    if (prefixed in style) {
      return css ? cssPrefixes[i] + property : prefixed;
    }
  }
}

export function styleProperty(prop) {
  return findProperty(prop, false);
}

export function cssProperty(cssProp) {
  return findProperty(cssProp, true);
}


================================================
FILE: addon/utils/translate.js
================================================
import { styleProperty, cssProperty } from './style-properties';

const transformCSSProp   = cssProperty('transform');
const transformStyleProp = styleProperty('transform');
export const supports3D  = !!styleProperty('perspectiveOrigin');
export const supports2D  = !!transformStyleProp;

export function translatePosition(el, x, y) {
  el.style.left = x+'px';
  el.style.top  = y+'px';
}

export function translateTransform2D(el, x, y) {
  el.style[transformStyleProp] = matrix2D(x, y);
}

export function translateTransform3D(el, x, y) {
  el.style[transformStyleProp] = matrix3D(x, y);
}

export function translatePositionCSS(x, y) {
  return `left:${x}px;top:${y}px;`;
}

export function translateTransform2DCSS(x, y) {
  return `${transformCSSProp}:${matrix2D(x, y)};`;
}

export function translateTransform3DCSS(x, y) {
  return `${transformCSSProp}:${matrix3D(x, y)};`;
}

function matrix2D(x, y) {
  return `matrix(1, 0, 0, 1, ${x}, ${y})`;
}

function matrix3D(x, y) {
  return `matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, ${x}, ${y}, 0, 1)`;
}

export const translate = (
  supports3D ? translateTransform3D : (
    supports2D ? translateTransform2D : translatePosition
  )
);

export const translateCSS = (
  supports3D ? translateTransform3DCSS : (
    supports2D ? translateTransform2DCSS : translatePositionCSS
  )
);


================================================
FILE: app/components/ember-collection.js
================================================
export { default } from 'ember-collection/components/ember-collection';


================================================
FILE: app/components/ember-native-scrollable.js
================================================
export { default } from 'ember-collection/components/ember-native-scrollable';


================================================
FILE: app/helpers/fixed-grid-layout.js
================================================
import { helper } from '@ember/component/helper';
import Grid from 'ember-collection/layouts/grid';

export default helper(function (params) {
  return new Grid(params[0], params[1]);
});


================================================
FILE: app/helpers/mixed-grid-layout.js
================================================
import { helper } from '@ember/component/helper';
import MixedGrid from 'ember-collection/layouts/mixed-grid';

export default helper(function (params) {
  return new MixedGrid(params[0]);
});


================================================
FILE: app/helpers/percentage-columns-layout.js
================================================
import { helper } from '@ember/component/helper';
import PercentageColumns from 'ember-collection/layouts/percentage-columns';

export default helper(function (params) {
  return new PercentageColumns(params[0], params[1], params[2]);
});


================================================
FILE: config/ember-try.js
================================================
'use strict';

const getChannelURL = require('ember-source-channel-url');

module.exports = async function() {
  return {
    useYarn: true,
    scenarios: [
      {
        name: 'ember-lts-3.28',
        npm: {
          devDependencies: {
            'ember-source': '~3.28.0'
          }
        }
      },
      {
        name: 'ember-lts-4.8',
        npm: {
          devDependencies: {
            'ember-source': '~4.8.0'
          }
        }
      },
      {
        name: 'ember-lts-4.12',
        npm: {
          devDependencies: {
            'ember-source': '~4.12.0'
          }
        }
      },
      {
        name: 'ember-release',
        npm: {
          devDependencies: {
            'ember-source': await getChannelURL('release')
          }
        },
        allowedToFail: true
      },
      {
        name: 'ember-beta',
        npm: {
          devDependencies: {
            'ember-source': await getChannelURL('beta')
          }
        },
        allowedToFail: true
      },
      {
        name: 'ember-canary',
        npm: {
          devDependencies: {
            'ember-source': await getChannelURL('canary')
          },
          allowedToFail: true
        }
      },
      // The default `.travis.yml` runs this scenario via `npm test`,
      // not via `ember try`. It's still included here so that running
      // `ember try:each` manually or from a customized CI config will run it
      // along with all the other scenarios.
      {
        name: 'ember-default',
        npm: {
          devDependencies: {}
        }
      }
    ]
  };
};


================================================
FILE: config/environment.js
================================================
'use strict';

module.exports = function(/* environment, appConfig */) {
  return { };
};


================================================
FILE: ember-cli-build.js
================================================
'use strict';

const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');

module.exports = function(defaults) {
  let app = new EmberAddon(defaults, {
    // Add options here
  });

  /*
    This build file specifies the options for the dummy test app of this
    addon, located in `/tests/dummy`
    This build file does *not* influence how the addon or the app using it
    behave. You most likely want to be modifying `./index.js` or app's build file
  */

  return app.toTree();
};


================================================
FILE: index.js
================================================
'use strict';

module.exports = {
  name: require('./package').name
};


================================================
FILE: package.json
================================================
{
  "name": "ember-collection",
  "version": "3.0.0",
  "description": "An efficient incremental rendering component with custom layouts and support for large lists.",
  "keywords": [
    "ember-addon"
  ],
  "repository": "https://github.com/adopted-ember-addons/ember-collection",
  "license": "MIT",
  "author": "Erik Bryn, Yapp Inc., and contributors.",
  "directories": {
    "doc": "doc",
    "test": "tests"
  },
  "scripts": {
    "build": "ember build",
    "changelog": "lerna-changelog",
    "lint:hbs": "ember-template-lint .",
    "lint:js": "eslint .",
    "release": "release-it",
    "start": "ember serve",
    "test": "ember test",
    "test:all": "ember try:each"
  },
  "dependencies": {
    "ember-cli-babel": "^7.26.6",
    "ember-cli-htmlbars": "^6.0.0",
    "layout-bin-packer": "^2.0.0"
  },
  "devDependencies": {
    "@ember/optional-features": "^2.0.0",
    "@ember/string": "^3.0.1",
    "@ember/test-helpers": "^2.9.3",
    "@glimmer/component": "^1.1.2",
    "@glimmer/tracking": "^1.1.2",
    "babel-eslint": "^10.1.0",
    "broccoli-asset-rev": "^3.0.0",
    "ember-auto-import": "^2.0.0",
    "ember-cli": "^4.12.0",
    "ember-cli-dependency-checker": "^3.3.0",
    "ember-cli-eslint": "^5.1.0",
    "ember-cli-inject-live-reload": "^2.1.0",
    "ember-cli-sri": "^2.1.1",
    "ember-cli-terser": "^4.0.2",
    "ember-compatibility-helpers": "^1.2.1",
    "ember-disable-prototype-extensions": "^1.1.3",
    "ember-load-initializers": "^2.1.2",
    "ember-maybe-import-regenerator": "^1.0.0",
    "ember-qunit": "^6.2.0",
    "ember-resolver": "^10.0.0",
    "ember-source": "^4.12.0",
    "ember-source-channel-url": "^3.0.0",
    "ember-template-lint": "^5.7.1",
    "ember-try": "^2.0.0",
    "eslint-plugin-ember": "^6.2.0",
    "eslint-plugin-node": "^9.0.1",
    "lerna-changelog": "^1.0.1",
    "loader.js": "^4.7.0",
    "qunit": "^2.13.0",
    "qunit-dom": "^2.0.0",
    "release-it": "^13.6.0",
    "release-it-lerna-changelog": "^2.3.0",
    "webpack": "^5.0.0"
  },
  "engines": {
    "node": ">= 16.*"
  },
  "publishConfig": {
    "registry": "https://registry.npmjs.org"
  },
  "ember": {
    "edition": "octane"
  },
  "ember-addon": {
    "configPath": "tests/dummy/config"
  },
  "release-it": {
    "plugins": {
      "release-it-lerna-changelog": {
        "infile": "CHANGELOG.md",
        "launchEditor": true
      }
    },
    "git": {
      "tagName": "v${version}"
    },
    "github": {
      "release": true,
      "tokenRef": "GITHUB_AUTH"
    }
  }
}


================================================
FILE: testem.js
================================================
module.exports = {
  test_page: 'tests/index.html?hidepassed',
  disable_watching: true,
  launch_in_ci: [
    'Chrome',
    'Firefox'
  ],
  launch_in_dev: [
    'Chrome',
    'Firefox',
    'Safari'
  ],
  browser_args: {
    Chrome: {
      ci: [
        // --no-sandbox is needed when running Chrome inside a container
        process.env.CI ? '--no-sandbox' : null,
        '--headless',
        '--disable-dev-shm-usage',
        '--disable-software-rasterizer',
        '--mute-audio',
        '--remote-debugging-port=0',
        '--window-size=1440,900'
      ].filter(Boolean)
    },
    Firefox: {
      mode: 'ci',
      args: [
        '--headless',
        '--window-size=1440,900'
      ]
    }
  }
};


================================================
FILE: tests/acceptance/list-view-test.js
================================================
import { currentURL, visit } from '@ember/test-helpers';
import { run } from '@ember/runloop';
import { module, skip } from 'qunit';
import startApp from '../../tests/helpers/start-app';

module('Acceptance | ember collection', function(hooks) {
  hooks.beforeEach(function() {
    this.application = startApp();
  });

  hooks.afterEach(function() {
    run(this.application, 'destroy');
  });

  skip('visiting /list-view', async function(assert) {
    await visit('/list-view');

    assert.equal(currentURL(), '/list-view');
  });

  /* FOLLOWING IS OLD ACCEPTANCE TEST CODE THAT NEEDS TO BE REWRITTEN */
  /*
  import Ember from 'ember';
  import { test } from 'ember-qunit';
  import moduleForView from '../helpers/module-for-view';
  import {
    compile,
    generateContent,
    sortElementsByPosition,
    itemPositions
  } from '../helpers/helpers';

  moduleForView("list-view", "acceptance", {});

  test("should render an empty view when there is no content", function(assert) {
    var content = generateContent(0),
      height = 500,
      rowHeight = 50,
      emptyViewHeight = 170,
      itemViewClass = ListItemView.extend({
        template: compile("{{name}}")
      }),
      emptyView = Ember.View.extend({
        attributeBindings: ['style'],
        classNames: ['empty-view'],
        style: 'height:' + emptyViewHeight + 'px;'
      });

    var view;
    Ember.run(this, function(){
      view = this.subject({
        content: content,
        height: height,
        rowHeight: rowHeight,
        itemViewClass: itemViewClass,
        emptyView: emptyView
      });
    });

    this.render();

    assert.equal(view.get('element').style.height, "500px", "The list view height is correct");
    assert.equal(this.$('.ember-list-container').height(), emptyViewHeight, "The scrollable view has the correct height");

    assert.equal(this.$('.ember-list-item-view').length, 0, "The correct number of rows were rendered");
    assert.equal(this.$('.empty-view').length, 1, "The empty view rendered");

    Ember.run(function () {
      view.set('content', generateContent(10));
    });

    assert.equal(view.get('element').style.height, "500px", "The list view height is correct");
    assert.equal(this.$('.ember-list-container').height(), 500, "The scrollable view has the correct height");

    assert.equal(this.$('.ember-list-item-view').length, 10, "The correct number of rows were rendered");
    assert.equal(this.$('.empty-view').length, 0, "The empty view is removed");

    Ember.run(function () {
      view.set('content', content);
    });

    assert.equal(view.get('element').style.height, "500px", "The list view height is correct");
    assert.equal(this.$('.ember-list-container').height(), emptyViewHeight, "The scrollable view has the correct height");

    assert.equal(this.$('.ember-list-item-view').length, 0, "The correct number of rows were rendered");
    assert.equal(this.$('.empty-view').length, 1, "The empty view rendered");

    Ember.run(function () {
      view.set('content', generateContent(10));
    });

    assert.equal(view.get('element').style.height, "500px", "The list view height is correct");
    assert.equal(this.$('.ember-list-container').height(), 500, "The scrollable view has the correct height");

    assert.equal(this.$('.ember-list-item-view').length, 10, "The correct number of rows were rendered");
    assert.equal(this.$('.empty-view').length, 0, "The empty view has been removed");
  });

  test("should render a subset of the full content, based on the height, in the correct positions", function(assert) {
    var content = generateContent(100),
      height = 500,
      rowHeight = 50,
      itemViewClass = ListItemView.extend({
        template: compile("{{name}}")
      });

    var view;
    Ember.run(this, function(){
      view = this.subject({
        content: content,
        height: height,
        rowHeight: rowHeight,
        itemViewClass: itemViewClass
      });
    });

    this.render();

    assert.equal(view.get('element').style.height, "500px", "The list view height is correct");
    assert.equal(this.$('.ember-list-container').height(), 5000, "The scrollable view has the correct height");

    var positionSorted = sortElementsByPosition(this.$('.ember-list-item-view'));

    assert.equal(this.$('.ember-list-item-view').length, 11, "The correct number of rows were rendered");
    assert.equal(Ember.$(positionSorted[0]).text(), "Item 1");
    assert.equal(Ember.$(positionSorted[10]).text(), "Item 11");

    assert.deepEqual(itemPositions(view).map(yPosition), [0, 50, 100, 150, 200, 250, 300, 350, 400, 450, 500]);
  });

  test("should render correctly with an initial scrollTop", function(assert) {
    var content = generateContent(100),
      height = 500,
      rowHeight = 50,
      itemViewClass = ListItemView.extend({
        template: compile("{{name}}")
      });

    var view;
    Ember.run(this, function(){
      view = this.subject({
        content: content,
        height: height,
        rowHeight: rowHeight,
        itemViewClass: itemViewClass,
        scrollTop: 475
      });
    });

    this.render();

    assert.equal(this.$('.ember-list-item-view').length, 11, "The correct number of rows were rendered");

    var positionSorted = sortElementsByPosition(this.$('.ember-list-item-view'));

    assert.equal(Ember.$(positionSorted[0]).text(), "Item 10");
    assert.equal(Ember.$(positionSorted[10]).text(), "Item 20");

    assert.deepEqual(itemPositions(view).map(yPosition), [450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950], "The rows are in the correct positions");
  });

  test("should perform correct number of renders and repositions on short list init", function (assert) {
    var content = generateContent(8),
      height = 60,
      width = 50,
      rowHeight = 10,
      positions = 0,
      renders = 0,
      itemViewClass = ListItemView.extend({
        template: compile("{{name}}")
      });

    Ember.subscribe("view.updateContext.render", {
      before: function(){},
      after: function(name, timestamp, payload) {
        renders++;
      }
    });

    Ember.subscribe("view.updateContext.positionElement", {
      before: function(){},
      after: function(name, timestamp, payload) {
        positions++;
      }
    });

    var view;
    Ember.run(this, function(){
      view = this.subject({
        content: content,
        height: height,
        width: width,
        rowHeight: rowHeight,
        itemViewClass: itemViewClass,
        scrollTop: 0
      });
    });

    this.render();

    assert.equal(renders, 7, "The correct number of renders occured");
    assert.equal(positions, 14, "The correct number of positions occured");
  });

  test("should perform correct number of renders and repositions while short list scrolling", function (assert) {
    var content = generateContent(8),
      height = 60,
      width = 50,
      scrollTop = 50,
      rowHeight = 10,
      positions = 0,
      renders = 0,
      itemViewClass = ListItemView.extend({
        template: compile("{{name}}")
      });

    if (window.console) {
      Ember.ENABLE_PROFILING = true;
    }

    Ember.subscribe("view.updateContext.render", {
      before: function(){},
      after: function(name, timestamp, payload) {
        renders++;
      }
    });

    Ember.subscribe("view.updateContext.positionElement", {
      before: function(){},
      after: function(name, timestamp, payload) {
        positions++;
      }
    });

    var view;
    Ember.run(this, function(){
      view = this.subject({
        content: content,
        height: height,
        width: width,
        rowHeight: rowHeight,
        itemViewClass: itemViewClass,
        scrollTop: 0
      });
    });

    this.render();

    Ember.run(function () {
      view.scrollTo(scrollTop);
    });

    assert.equal(renders, 14, "The correct number of renders occured");
    assert.equal(positions, 21, "The correct number of positions occured");
  });

  test("should perform correct number of renders and repositions on long list init", function (assert) {
    var content = generateContent(200),
      height = 50,
      width = 50,
      rowHeight = 10,
      positions = 0,
      renders = 0,
      itemViewClass = ListItemView.extend({
        template: compile("{{name}}")
      });

    Ember.subscribe("view.updateContext.render", {
      before: function(){},
      after: function(name, timestamp, payload) {
        renders++;
      }
    });

    Ember.subscribe("view.updateContext.positionElement", {
      before: function(){},
      after: function(name, timestamp, payload) {
        positions++;
      }
    });

    var view;
    Ember.run(this, function(){
      view = this.subject({
        content: content,
        height: height,
        width: width,
        rowHeight: rowHeight,
        itemViewClass: itemViewClass,
        scrollTop: 0
      });
    });

    this.render();

    assert.equal(renders, ((height / 10) + 1),  "The correct number of renders occurred");
    assert.equal(positions, 12, "The correct number of positions occurred");
  });

  test("should be programatically scrollable", function(assert) {
    var content = generateContent(100),
      height = 500,
      rowHeight = 50,
      itemViewClass = ListItemView.extend({
        template: compile("{{name}}")
      });

    var view;
    Ember.run(this, function(){
      view = this.subject({
        content: content,
        height: height,
        rowHeight: rowHeight,
        itemViewClass: itemViewClass
      });
    });

    this.render();

    Ember.run(function() {
      view.scrollTo(475);
    });

    assert.equal(this.$('.ember-list-item-view').length, 11, "The correct number of rows were rendered");
    assert.deepEqual(itemPositions(view).map(yPosition), [450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950], "The rows are in the correct positions");
  });

  test("height change", function(assert){
    var content = generateContent(100),
      height = 500,
      rowHeight = 50,
      itemViewClass = ListItemView.extend({
        template: compile("{{name}}")
      });

    var view;
    Ember.run(this, function(){
      view = this.subject({
        content: content,
        height: height,
        rowHeight: rowHeight,
        itemViewClass: itemViewClass
      });
    });

    this.render();

    assert.equal(this.$('.ember-list-item-view').length, 11, "The correct number of rows were rendered");
    assert.deepEqual(itemPositions(view).map(yPosition), [0, 50, 100, 150, 200, 250, 300, 350, 400, 450, 500], "The rows are in the correct positions");

    Ember.run(function() {
      view.set('height', 100);
    });

    assert.equal(this.$('.ember-list-item-view').length, 3, "The correct number of rows were rendered");
    assert.deepEqual(itemPositions(view).map(yPosition), [0, 50, 100], "The rows are in the correct positions");

    Ember.run(function() {
      view.set('height', 50);
    });

    assert.equal(this.$('.ember-list-item-view').length, 2, "The correct number of rows were rendered");
    assert.deepEqual(itemPositions(view).map(yPosition), [0, 50], "The rows are in the correct positions");

    Ember.run(function() {
      view.set('height', 100);
    });

    assert.equal(this.$('.ember-list-item-view').length, 3, "The correct number of rows were rendered");
    assert.deepEqual(itemPositions(view).map(yPosition), [0, 50, 100], "The rows are in the correct positions" );
  });

  test("adding a column, when everything is already within viewport", function(assert){
    // start off with 2x3 grid visible and 4 elements
    // x: visible, +: padding w/ element, 0: element not-drawn, o: padding w/o element, ?: no element
    //
    // x x  --|
    // x x    |- viewport
    // ? ?  --|
    var content = generateContent(4),
      width = 100,
      height = 150,
      rowHeight = 50,
      elementWidth = 50,
      itemViewClass = ListItemView.extend({
        template: compile("A:{{name}}{{view view.NestedViewClass}}"),
        NestedViewClass: Ember.View.extend({
          tagName: 'span',
          template: compile("B:{{name}}")
        })
      });

    var view;
    Ember.run(this, function(){
      view = this.subject({
        content: content,
        width: width,
        height: height,
        rowHeight: rowHeight,
        elementWidth: elementWidth,
        itemViewClass: itemViewClass,
        scrollTop: 0
      });
    });

    this.render();

    assert.deepEqual(itemPositions(view), [
      { x:  0, y:    0 }, { x: 50, y:    0 },
      { x:  0, y:   50 }, { x: 50, y:   50 }
    ], "initial render: The rows are rendered in the correct positions");

    assert.equal(this.$('.ember-list-item-view').length, 4, "initial render: The correct number of rows were rendered");

    // rotate to a with 3x2 grid visible and 8 elements
    // rapid dimension changes
    Ember.run(function() {
      view.set('width',  140);
    });

    Ember.run(function() {
      view.set('width',  150);
    });

    // x: visible, +: padding w/ element, 0: element not-drawn, o: padding w/o element
    //
    // x x x --|
    // x ? ?   |- viewport
    // ? ? ? --|

    assert.equal(this.$('.ember-list-item-view').length, 4, "after width + height change: the correct number of rows were rendered");

    assert.deepEqual(itemPositions(view), [
      { x:  0, y:  0 }, { x: 50, y: 0 }, { x: 100, y: 0 },
      { x:  0, y: 50 }
    ], "after width + height change: The rows are in the correct positions");

    var sortedElements = sortElementsByPosition(this.$('.ember-list-item-view'));
    var texts = Ember.$.map(sortedElements, function(el){ return Ember.$(el).text(); });
    assert.deepEqual(texts, [
      'A:Item 1B:Item 1',
      'A:Item 2B:Item 2',
      'A:Item 3B:Item 3',
      'A:Item 4B:Item 4'
    ], 'after width + height change: elements should be rendered in expected position');
  });

  test("height and width change after with scroll – simple", function(assert){
    // start off with 2x3 grid visible and 10 elements, at top of scroll
    // x: visible, +: padding w/ element, 0: element not-drawn, o: padding w/o element
    //
    // x x  --|
    // x x    |- viewport
    // x x  --|
    // + +
    // 0 0
    var content = generateContent(10),
      width = 100,
      height = 150,
      rowHeight = 50,
      elementWidth = 50,
      itemViewClass = ListItemView.extend({
        template: compile("A:{{name}}{{view view.NestedViewClass}}"),
        NestedViewClass: Ember.View.extend({
          tagName: 'span',
          template: compile("B:{{name}}")
        })
      });

    var view;
    Ember.run(this, function(){
      view = this.subject({
        content: content,
        width: width,
        height: height,
        rowHeight: rowHeight,
        elementWidth: elementWidth,
        itemViewClass: itemViewClass,
        scrollTop: 0
      });
    });

    this.render();

    assert.deepEqual(itemPositions(view), [
      { x:  0, y:    0 }, { x: 50, y:    0 },
      { x:  0, y:   50 }, { x: 50, y:   50 },
      { x:  0, y:  100 }, { x: 50, y:  100 },
      { x:  0, y:  150 }, { x: 50, y:  150 }
    ], "initial render: The rows are rendered in the correct positions");

    assert.equal(this.$('.ember-list-item-view').length, 8, "initial render: The correct number of rows were rendered");

    // user is scrolled near the bottom of the list
    Ember.run(function(){
      view.scrollTo(101);
    });
    // x: visible, +: padding w/ element, 0: element not-drawn, o: padding w/o element
    //
    // 0 0
    // o o
    // x x --|
    // x x   |- viewport
    // x x --|

    assert.equal(this.$('.ember-list-item-view').length, 8, "after scroll: The correct number of rows were rendered");

    assert.deepEqual(itemPositions(view), [
      { x: 0, y:  50 }, { x: 50, y:  50 },
      { x: 0, y: 100 }, { x: 50, y: 100 },
      { x: 0, y: 150 }, { x: 50, y: 150 },
      /* padding / { x: 0, y: 200 }, { x: 50, y: 200 }], "after scroll: The rows are in the correct positions");

    // rotate to a with 3x2 grid visible and 8 elements
    Ember.run(function() {
      view.set('width',  150);
      view.set('height', 100);
    });

    // x: visible, +: padding w/ element, 0: element not-drawn, o: padding w/o element
    //
    // 0 0 0
    // x x x
    // x x x --|
    // x o o --|- viewport

    assert.equal(this.$('.ember-list-item-view').length, 9, "after width + height change: the correct number of rows were rendered");

    assert.deepEqual(itemPositions(view), [
      /*              /  { x:  50, y:   0 }, { x: 100, y:   0 },
      { x:   0, y:  50 }, { x:  50, y:  50 }, { x: 100, y:  50 },
      { x:   0, y: 100 }, { x:  50, y: 100 }, { x: 100, y: 100 },
      { x:   0, y: 150 }], "after width + height change: The rows are in the correct positions");

    var sortedElements = sortElementsByPosition(this.$('.ember-list-item-view'));
    var texts = Ember.$.map(sortedElements, function(el){ return Ember.$(el).text(); });
    assert.deepEqual(texts, [
      'A:Item 2B:Item 2',
      'A:Item 3B:Item 3',
      'A:Item 4B:Item 4',
      'A:Item 5B:Item 5',
      'A:Item 6B:Item 6',
      'A:Item 7B:Item 7',
      'A:Item 8B:Item 8',
      'A:Item 9B:Item 9',
      'A:Item 10B:Item 10'
    ], 'after width + height change: elements should be rendered in expected position');
  });

  test("height and width change after with scroll – 1x2 -> 2x2 with 5 items", function(assert){
    // x: visible, +: padding w/ element, 0: element not-drawn, o: padding w/o element
    //
    // x  --|
    // x  --|- viewport
    // +
    // 0
    // 0
    var content = generateContent(5),
      width = 50,
      height = 100,
      rowHeight = 50,
      elementWidth = 50,
      itemViewClass = ListItemView.extend({
        template: compile("A:{{name}}{{view view.NestedViewClass}}"),
        NestedViewClass: Ember.View.extend({
          tagName: 'span',
          template: compile("B:{{name}}")
        })
      });

    var view;
    Ember.run(this, function(){
      view = this.subject({
        content: content,
        width: width,
        height: height,
        rowHeight: rowHeight,
        elementWidth: elementWidth,
        itemViewClass: itemViewClass,
        scrollTop: 0
      });
    });

    this.render();

    assert.deepEqual(itemPositions(view), [
      { x:  0, y:    0 },
      { x:  0, y:   50 },
      { x:  0, y:  100 }
    ], "initial render: The rows are rendered in the correct positions");

    assert.equal(this.$('.ember-list-item-view').length, 3, "initial render: The correct number of rows were rendered");

    // user is scrolled near the bottom of the list
    Ember.run(function(){
      view.scrollTo(151);
    });
    // x: visible, +: padding w/ element, 0: element not-drawn, o: padding w/o element
    //
    // 0
    // 0
    // o
    // x --|
    // x --|- viewport
    // 0
    assert.equal(this.$('.ember-list-item-view').length, 3, "after scroll: The correct number of rows were rendered");

    assert.deepEqual(itemPositions(view), [
      { x: 0, y: 100 },
      { x: 0, y: 150 },
      /* padding / { x: 0, y: 200 }], "after scroll: The rows are in the correct positions");

    // rotate to a with 2x2 grid visible and 8 elements
    Ember.run(function() {
      view.set('width',  100);
      view.set('height', 100);
    });

    // x: visible, +: padding w/ element, 0: element not-drawn, o: padding w/o element
    //
    // 0 0
    // x x --|
    // x o --|- viewport
    // o
    assert.equal(this.$('.ember-list-item-view').length, 5, "after width + height change: the correct number of rows were rendered");

    assert.deepEqual(itemPositions(view), [
      { x: 0, y:   0 }, { x: 50, y:   0 },
      { x: 0, y:  50 }, { x: 50, y:  50 },
      { x: 0, y: 100 }
    ], "The rows are in the correct positions");

    var sortedElements = sortElementsByPosition(this.$('.ember-list-item-view'));
    var texts = Ember.$.map(sortedElements, function(el){ return Ember.$(el).text(); });

    assert.deepEqual(texts, [
      'A:Item 1B:Item 1', 'A:Item 2B:Item 2',
      'A:Item 3B:Item 3', 'A:Item 4B:Item 4',
      'A:Item 5B:Item 5'
    ], 'elements should be rendered in expected position');
  });

  test("elementWidth change", function(assert){
    var i,
      positionSorted,
      content = generateContent(100),
      height = 200,
      width = 200,
      rowHeight = 50,
      elementWidth = 100,
      itemViewClass = ListItemView.extend({
        template: compile("{{name}}")
      });

    var view;
    Ember.run(this, function(){
      view = this.subject({
        content: content,
        height: height,
        width: width,
        rowHeight: rowHeight,
        itemViewClass: itemViewClass,
        elementWidth: elementWidth
      });
    });

    this.render();

    assert.equal(this.$('.ember-list-item-view').length, 10, "The correct number of rows were rendered");
    assert.deepEqual(itemPositions(view), [
      { x:0,   y: 0   },
      { x:100, y: 0   },
      { x:0,   y: 50  },
      { x:100, y: 50  },
      { x:0 ,  y: 100 },
      { x:100, y: 100 },
      { x:0,   y: 150 },
      { x:100, y: 150 },
      { x:0,   y: 200 },
      { x:100, y: 200 }], "The rows are in the correct positions");

    positionSorted = sortElementsByPosition(this.$('.ember-list-item-view'));

    for(i = 0; i < 10; i++) {
      assert.equal(Ember.$(positionSorted[i]).text(), "Item " + (i+1));
    }

    Ember.run(function() {
      view.set('width', 100);
    });

    assert.equal(this.$('.ember-list-item-view').length, 5, "The correct number of rows were rendered");

    positionSorted = sortElementsByPosition(this.$('.ember-list-item-view'));

    assert.deepEqual(itemPositions(view), [
      { x: 0, y: 0},
      { x: 0, y: 50},
      { x: 0, y: 100},
      { x: 0, y: 150},
      { x: 0, y: 200}
    ], "The rows are in the correct positions");

    for(i = 0; i < 5; i++) {
      assert.equal(Ember.$(positionSorted[i]).text(), "Item " + (i+1));
    }

    // Test a width smaller than elementWidth, should behave the same as width === elementWidth
    Ember.run(function () {
      view.set('width', 50);
    });

    assert.equal(this.$('.ember-list-item-view').length, 5, "The correct number of rows were rendered");

    positionSorted = sortElementsByPosition(this.$('.ember-list-item-view'));

    assert.deepEqual(itemPositions(view), [
      { x: 0, y: 0},
      { x: 0, y: 50},
      { x: 0, y: 100},
      { x: 0, y: 150},
      { x: 0, y: 200}
    ], "The rows are in the correct positions");

    for(i = 0; i < 5; i++) {
      assert.equal(Ember.$(positionSorted[i]).text(), "Item " + (i+1));
    }

    assert.ok(this.$().is('.ember-list-view-list'), 'has correct list related class');

    Ember.run(function() {
      view.set('width', 200);
    });

    assert.ok(this.$().is('.ember-list-view-grid'), 'has correct grid related class');
    positionSorted = sortElementsByPosition(this.$('.ember-list-item-view'));
    assert.equal(this.$('.ember-list-item-view').length, 10, "The correct number of rows were rendered");
    assert.deepEqual(itemPositions(view), [
      { x:0,   y: 0   },
      { x:100, y: 0   },
      { x:0,   y: 50  },
      { x:100, y: 50  },
      { x:0 ,  y: 100 },
      { x:100, y: 100 },
      { x:0,   y: 150 },
      { x:100, y: 150 },
      { x:0,   y: 200 },
      { x:100, y: 200 }], "The rows are in the correct positions");

    for(i = 0; i < 10; i++) {
      assert.equal(Ember.$(positionSorted[i]).text(), "Item " + (i+1));
    }
  });

  test("elementWidth change with scroll", function(assert){
    var i,
      positionSorted,
      content = generateContent(100),
      height = 200,
      width = 200,
      rowHeight = 50,
      elementWidth = 100,
      itemViewClass = ListItemView.extend({
        template: compile("{{name}}")
      });

    var view;
    Ember.run(this, function(){
      view = this.subject({
        content: content,
        height: height,
        width: width,
        rowHeight: rowHeight,
        itemViewClass: itemViewClass,
        elementWidth: elementWidth
      });
    });

    this.render();

    Ember.run(function(){
      view.scrollTo(1000);
    });

    assert.equal(this.$('.ember-list-item-view').length, 10, "after scroll 1000 - The correct number of rows were rendered");
    assert.deepEqual(itemPositions(view), [
      { x:0,   y: 1000 },
      { x:100, y: 1000 },
      { x:0,   y: 1050 },
      { x:100, y: 1050 },
      { x:0 ,  y: 1100 },
      { x:100, y: 1100 },
      { x:0,   y: 1150 },
      { x:100, y: 1150 },
      { x:0,   y: 1200 },
      { x:100, y: 1200 }], "after scroll 1000 - The rows are in the correct positions");

    positionSorted = sortElementsByPosition(this.$('.ember-list-item-view'));

    for (i = 0; i < 10; i++) {
      assert.equal(Ember.$(positionSorted[i]).text(), "Item " + (i + 41));
    }

    Ember.run(function() {
      view.set('width', 100);
    });

    assert.equal(this.$('.ember-list-item-view').length, 5, " after width 100 -The correct number of rows were rendered");

    positionSorted = sortElementsByPosition(this.$('.ember-list-item-view'));

    assert.deepEqual(itemPositions(view), [
      { x:0,   y: 2000 },
      { x:0,   y: 2050 },
      { x:0 ,  y: 2100 },
      { x:0,   y: 2150 },
      { x:0,   y: 2200 }], "after width 100 - The rows are in the correct positions");

    for(i = 0; i < 5; i++) {
      assert.equal(Ember.$(positionSorted[i]).text(), "Item " + (i + 41));
    }

    Ember.run(function() {
      view.set('width', 200);
    });

    positionSorted = sortElementsByPosition(this.$('.ember-list-item-view'));
    assert.equal(this.$('.ember-list-item-view').length, 10, "after width 200 - The correct number of rows were rendered");
    assert.deepEqual(itemPositions(view), [
      { x:0,   y: 1000 },
      { x:100, y: 1000 },
      { x:0,   y: 1050 },
      { x:100, y: 1050 },
      { x:0 ,  y: 1100 },
      { x:100, y: 1100 },
      { x:0,   y: 1150 },
      { x:100, y: 1150 },
      { x:0,   y: 1200 },
      { x:100, y: 1200 }], "after width 200 - The rows are in the correct positions");

    for(i = 0; i < 10; i++) {
      assert.equal(Ember.$(positionSorted[i]).text(), "Item " + (i + 41));
    }
  });

  test("A property of an item can be changed", function(assert) {
    var content = generateContent(100),
      height = 500,
      rowHeight = 50,
      itemViewClass = ListItemView.extend({
        template: compile("{{name}}")
      });

    var view;
    Ember.run(this, function(){
      view = this.subject({
        content: content,
        height: height,
        rowHeight: rowHeight,
        itemViewClass: itemViewClass
      });
    });

    this.render();

    //Change name
    Ember.run(function() {
      content.set('0.name', 'First change');
    });

    assert.equal(this.$('.ember-list-item-view:eq(0)').text(), "First change", "The item's name has been updated");

    //Scroll down, change name, and scroll back up
    Ember.run(function() {
      view.scrollTo(600);
    });

    Ember.run(function() {
      content.set('0.name', 'Second change');
    });

    Ember.run(function() {
      view.scrollTo(0);
    });

    assert.equal(this.$('.ember-list-item-view:eq(0)').text(), "Second change", "The item's name has been updated");

  });

  test("The list view is wrapped in an extra div to support JS-emulated scrolling", function(assert) {
    var view;
    Ember.run(this, function(){
      view = this.subject({
        content: Ember.A(),
        height: 100,
        rowHeight: 50
      });
    });

    this.render();
    assert.equal(this.$('.ember-list-container').length, 1, "expected a ember-list-container wrapper div");
    assert.equal(this.$('.ember-list-container > .ember-list-item-view').length, 0, "expected ember-list-items inside the wrapper div");
  });

  test("When scrolled past the totalHeight, views should not be recycled in. This is to support overscroll", function(assert) {
    var view;
    Ember.run(this, function(){
      view = this.subject({
        content: generateContent(2),
        height:100,
        rowHeight: 50,
        itemViewClass: ListItemView.extend({
          template: compile("Name: {{name}}")
        })
      });
    });

    this.render();

    Ember.run(function(){
      view.scrollTo(150);
    });

    var positionSorted = sortElementsByPosition(this.$('.ember-list-item-view'));
    assert.equal(this.$('.ember-list-item-view').length, 2, "after width 200 - The correct number of rows were rendered");

    assert.deepEqual(itemPositions(view), [
      { x:0, y:  0 },
      { x:0, y: 50 }] , "went beyond scroll max via overscroll");

    assert.equal(Ember.$(positionSorted[0]).text(), "Name: Item " + 1);
    assert.equal(Ember.$(positionSorted[1]).text(), "Name: Item " + 2);
  });


  test("When list-view is unable to scroll, scrollTop should be zero", function(assert) {
    var view;
    Ember.run(this, function(){
      view = this.subject({
        content: generateContent(2),
        height:400,
        rowHeight: 100,
        itemViewClass: ListItemView.extend({
          template: compile("Name: {{name}}")
        })
      });
    });

    this.render();

    Ember.run(function(){
      view.scrollTo(1);
    });

    assert.equal(view.get('scrollTop'), 0, "Scrolltop should be zero");
  });


  test("Creating a ListView without height and rowHeight properties should throw an exception", function(assert) {
    assert.throws(()=>{
        Ember.run(()=>{
          this.subject({
            content: generateContent(4)
          });
        });
        this.render();
      },
      /A ListView must be created with a height and a rowHeight./, "Throws exception.");
  });

  test("Creating a ListView without height and rowHeight properties should throw an exception", function(assert) {
    assert.throws(()=>{
        Ember.run(()=>{
          this.subject({
            content: generateContent(4)
          });
        });

        this.render();
      },
      /A ListView must be created with a height and a rowHeight./, "Throws exception.");
  });

  test("handle strange ratios between height/rowHeight", function(assert) {
    var view;
    Ember.run(this, function(){
      view = this.subject({
        content: generateContent(15),
        height: 235,
        rowHeight: 73,
        itemViewClass: ListItemView.extend({
          template: compile("Name: {{name}}")
        })
      });
    });

    this.render();

    var positionSorted = sortElementsByPosition(this.$('.ember-list-item-view'));
    assert.equal(this.$('.ember-list-item-view').length, 5);

    assert.deepEqual(itemPositions(view), [
      { x:0, y:   0 },
      { x:0, y:  73 },
      { x:0, y: 146 },
      { x:0, y: 219 },
      { x:0, y: 292 }
    ] , "went beyond scroll max via overscroll");

    for (var i = 0; i < positionSorted.length; i++) {
      assert.equal(Ember.$(positionSorted[i]).text(), "Name: Item " + (i + 1));
    }

    view.scrollTo(1000);

    positionSorted = sortElementsByPosition(this.$('.ember-list-item-view'));
    assert.equal(this.$('.ember-list-item-view').length, 5);

    // expected
    // -----
    // 0   |
    // 1   |
    // 2   |
    // 3   |
    // 4   | <--- not rendered
    // 5   |
    // 6   |
    // 7   |
    // 8   |
    // 9   |
    // ----
    // 10  | <- buffer
    // ----
    // 11  | <-- partially visible
    // 12  | <--- visible
    // 13  |
    // 14  |
    // ----
    assert.deepEqual(itemPositions(view), [
      { x:0, y:  730 }, // <-- buffer
      { x:0, y:  803 }, // <-- partially visible
      { x:0, y:  876 }, // <-- in view
      { x:0, y:  949 }, // <-- in view
      { x:0, y: 1022 }  // <-- in view
    ], "went beyond scroll max via overscroll");

    assert.equal(Ember.$(positionSorted[0]).text(), "Name: Item 11");
    assert.equal(Ember.$(positionSorted[1]).text(), "Name: Item 12");
    assert.equal(Ember.$(positionSorted[2]).text(), "Name: Item 13");
    assert.equal(Ember.$(positionSorted[3]).text(), "Name: Item 14");
    assert.equal(Ember.$(positionSorted[4]).text(), "Name: Item 15");
  });

  test("handle bindable rowHeight", function(assert) {
    var view;
    Ember.run(this, function(){
      view = this.subject({
        content: generateContent(15),
        height: 400,
        rowHeight: 100,
        itemViewClass: ListItemView.extend({
          template: compile("Name: {{name}}")
        })
      });
    });

    this.render();

    var positionSorted = sortElementsByPosition(this.$('.ember-list-item-view'));
    assert.equal(this.$('.ember-list-item-view').length, 5);
    assert.equal(view.get('totalHeight'), 1500);

    // expected
    // -----
    // 0   |
    // 1   |
    // 2   |
    // 3   |
    // -----
    // 4   | <--- buffer
    // -----
    // 5   |
    // 6   |
    // 7   |
    // 8   |
    // 9   |
    // 10  |
    // 11  |
    // 12  |
    // 13  |
    // 14  |
    // -----
    //
    assert.deepEqual(itemPositions(view), [
      { x:0, y:   0 }, // <- visible
      { x:0, y: 100 }, // <- visible
      { x:0, y: 200 }, // <- visible
      { x:0, y: 300 }, // <- visible
      { x:0, y: 400 }  // <- buffer
    ] , "inDOM views are correctly positioned: before rowHeight change");

    assert.equal(Ember.$(positionSorted[0]).text(), "Name: Item 1");
    assert.equal(Ember.$(positionSorted[1]).text(), "Name: Item 2");
    assert.equal(Ember.$(positionSorted[2]).text(), "Name: Item 3");
    assert.equal(Ember.$(positionSorted[3]).text(), "Name: Item 4");

    Ember.run(view, 'set', 'rowHeight', 200);

    positionSorted = sortElementsByPosition(this.$('.ember-list-item-view'));
    assert.equal(this.$('.ember-list-item-view').length, 3);
    assert.equal(view.get('totalHeight'), 3000);

    // expected
    // -----
    // 0   |
    // 1   |
    // ----|
    // 2   | <--- buffer
    // ----|
    // 3   |
    // 4   |
    // 5   |
    // 6   |
    // 7   |
    // 8   |
    // 9   |
    // 10  |
    // 11  |
    // 12  |
    // 13  |
    // 14  |
    // -----
    assert.deepEqual(itemPositions(view), [
      { x:0, y:    0 }, // <-- visible
      { x:0, y:  200 }, // <-- visible
      { x:0, y:  400 }  // <-- buffer
    ], "inDOM views are correctly positioned: after rowHeight change");

    assert.equal(Ember.$(positionSorted[0]).text(), "Name: Item 1");
    assert.equal(Ember.$(positionSorted[1]).text(), "Name: Item 2");
    assert.equal(Ember.$(positionSorted[2]).text(), "Name: Item 3");
  });

  var scrollYChanged, reuseChildren;

  moduleForView("list-view", "acceptance", {
    setup: function() {
      scrollYChanged = 0;
      reuseChildren = 0;
    },
    subject: function(options, factory) {
      return factory.extend({
        init: function () {
          this.on('scrollYChanged', function () {
            scrollYChanged++;
          });
          this._super();
        },
        _reuseChildren: function () {
          reuseChildren++;
          this._super();
        }
      }).create(options);
    }
  });

  test("should trigger scrollYChanged correctly", function (assert) {
    var view;
    Ember.run(this, function(){
      view = this.subject({
        content: generateContent(10),
        height: 100,
        rowHeight: 50
      });
    });

    this.render();

    assert.equal(scrollYChanged, 0, 'scrollYChanged should not fire on init');

    view.scrollTo(1);

    assert.equal(scrollYChanged, 1, 'scrollYChanged should fire after scroll');

    view.scrollTo(1);

    assert.equal(scrollYChanged, 1, 'scrollYChanged should not fire for same value');
  });

  moduleForView("list-view", "acceptance", {
    setup: function() {
      scrollYChanged = 0;
      reuseChildren = 0;
    },
    subject: function(options, factory) {
      return factory.extend({
        _reuseChildren: function () {
          reuseChildren++;
          this._super();
        }
      }).create(options);
    }
  });

  test("should trigger reuseChildren correctly", function (assert) {
    var view;
    Ember.run(this, function() {
      view = this.subject({
        content: generateContent(10),
        height: 100,
        rowHeight: 50
      });
    });

    this.render();

    assert.equal(reuseChildren, 1, 'initialize the content');

    view.scrollTo(1);

    assert.equal(reuseChildren, 1, 'should not update the content');

    view.scrollTo(51);

    assert.equal(reuseChildren, 2, 'should update the content');
  });

  function yPosition(position){
    return position.y;
  }

  function xPosition(position){
    return position.x;
  }
  */
});


================================================
FILE: tests/dummy/app/app.js
================================================
import Application from '@ember/application';
import Resolver from './resolver';
import loadInitializers from 'ember-load-initializers';
import config from './config/environment';

const App = Application.extend({
  modulePrefix: config.modulePrefix,
  podModulePrefix: config.podModulePrefix,
  Resolver
});

loadInitializers(App, config.modulePrefix);

export default App;


================================================
FILE: tests/dummy/app/components/.gitkeep
================================================


================================================
FILE: tests/dummy/app/controllers/.gitkeep
================================================


================================================
FILE: tests/dummy/app/controllers/mixed.js
================================================
import Controller from '@ember/controller';

export default class extends Controller {}


================================================
FILE: tests/dummy/app/controllers/percentages.js
================================================
import Controller from '@ember/controller';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';

export default class PercentagesController extends Controller {
  @tracked columns = [20, 60, 20];

  @action
  changeColumn(col) {
    switch (col) {
      case 1:
        this.columns = [25, 50, 25];
        break;
      case 2:
        this.columns = [20, 20, 40, 20];
        break;
      case 3:
        this.columns = [33.33, 33.33, 33.33];
        break;
      case 4:
        this.columns = [50, 50];
        break;
      case 5:
        this.columns = [100];
        break;
      default:
        this.columns = [50, 50];
        break;
      }
  }
}


================================================
FILE: tests/dummy/app/controllers/scroll-position.js
================================================
import Controller from '@ember/controller';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';

export default class ScrollPositionController extends Controller {
  @tracked itemWidth = 100;
  @tracked itemHeight = 100;
  @tracked containerWidth = 315;
  @tracked containerHeight = 600;
  @tracked scrollLeft = 0;
  @tracked scrollTop = 0;

  @action
  updateContainerWidth(ev) {
    this.containerWidth = parseInt(ev.target.value, 10);
  }

  @action
  updateContainerHeight(ev) {
    this.containerHeight = parseInt(ev.target.value, 10);
  }

  @action
  makeSquare() {
    this.itemWidth = 100;
    this.itemHeight = 100;
  }

  @action
  makeRow() {
    this.itemWidth = 300;
    this.itemHeight = 100;
  }

  @action
  makeLongRect() {
    this.itemWidth = 100;
    this.itemHeight = 50;
  }

  @action
  makeTallRect() {
    this.itemWidth = 50;
    this.itemHeight = 100;
  }

  @action
  scrollChange(scrollLeft, scrollTop){
    this.scrollLeft = scrollLeft;
    this.scrollTop = scrollTop;
  }
}

================================================
FILE: tests/dummy/app/controllers/simple.js
================================================
import Controller from '@ember/controller';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';

function shuffle(array) {
  var currentIndex = array.length, temporaryValue, randomIndex ;

  // While there remain elements to shuffle...
  while (0 !== currentIndex) {

    // Pick a remaining element...
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex -= 1;

    // And swap it with the current element.
    temporaryValue = array[currentIndex];
    array[currentIndex] = array[randomIndex];
    array[randomIndex] = temporaryValue;
  }

  return array;
}

export default class SimpleController extends Controller {
  @tracked itemWidth = 100;
  @tracked itemHeight = 100;
  @tracked containerWidth = 315;
  @tracked containerHeight = 600;

  @action
  updateContainerWidth(ev) {
    this.containerWidth = parseInt(ev.target.value, 10);
  }
  
  @action
  updateContainerHeight(ev) {
    this.containerHeight = parseInt(ev.target.value, 10);
  }

  @action
  shuffle() {
      this.model = shuffle(this.get('model').slice(0));
  }

  @action
  makeSquare() {
    this.itemWidth = 100;
    this.itemHeight = 100;
  }

  @action
  makeRow() {
    this.itemWidth = 300;
    this.itemHeight = 100;
  }

  @action
  makeLongRect() {
    this.itemWidth = 100;
    this.itemHeight = 50;
  }

  @action
  makeTallRect() {
    this.itemWidth = 50;
    this.itemHeight = 100;
  }
}

================================================
FILE: tests/dummy/app/helpers/.gitkeep
================================================


================================================
FILE: tests/dummy/app/helpers/size-to-style.js
================================================
import { htmlSafe } from '@ember/template';
import { helper } from '@ember/component/helper';

export default helper(function ([width, height]) {
  return htmlSafe(`position: relative; width: ${width}px; height: ${height}px;`);
});


================================================
FILE: tests/dummy/app/index.html
================================================
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Ember Collection Demos</title>
    <meta name="description" content="">
    <meta name="viewport" content="initial-scale=1, user-scalable=no">

    {{content-for "head"}}

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
    <link integrity="" rel="stylesheet" href="{{rootURL}}assets/vendor.css">
    <link integrity="" rel="stylesheet" href="{{rootURL}}assets/dummy.css">

    {{content-for "head-footer"}}
  </head>
  <body>
    {{content-for "body"}}

    <script src="{{rootURL}}assets/vendor.js"></script>
    <script src="{{rootURL}}assets/dummy.js"></script>

    {{content-for "body-footer"}}
  </body>
</html>


================================================
FILE: tests/dummy/app/models/.gitkeep
================================================


================================================
FILE: tests/dummy/app/resolver.js
================================================
import Resolver from 'ember-resolver';

export default Resolver;


================================================
FILE: tests/dummy/app/router.js
================================================
import EmberRouter from '@ember/routing/router';
import config from './config/environment';

const Router = EmberRouter.extend({
  location: config.locationType,
  rootURL: config.rootURL
});

Router.map(function() {
  this.route('simple');
  this.route('scroll-position');
  this.route('mixed');
  this.route('percentages');
});

export default Router;


================================================
FILE: tests/dummy/app/routes/.gitkeep
================================================


================================================
FILE: tests/dummy/app/routes/mixed.js
================================================
import Route from '@ember/routing/route';

function getRandomInt() {
  return Math.floor(Math.random() * (251) + 75);
}
export default Route.extend({
  model: function() {
    var items = [];
    for (var i = 0; i < 1000; i++) {
      var width = getRandomInt();
      var height = getRandomInt();
      items.push({
        name: 'Item ' + (i + 1) + '(' + width + 'x' + height + ')',
        width: width,
        height: height
      });
    }

    return items;
  }
});


================================================
FILE: tests/dummy/app/routes/percentages.js
================================================
import Route from '@ember/routing/route';
import makeModel from '../utils/make-model';

export default Route.extend({
  model: makeModel()
});


================================================
FILE: tests/dummy/app/routes/scroll-position.js
================================================
import Route from '@ember/routing/route';
import makeModel from '../utils/make-model';

export default Route.extend({
  model: makeModel()
});


================================================
FILE: tests/dummy/app/routes/simple.js
================================================
import Route from '@ember/routing/route';
import makeModel from '../utils/make-model';

export default Route.extend({
  model: makeModel()
});


================================================
FILE: tests/dummy/app/styles/app.css
================================================
body {
   padding-top: 70px;
}

code {
    padding: 0;
    padding-top: 0.2em;
    padding-bottom: 0.2em;
    margin: 0;
    font-size: 85%;
    background-color: rgba(0,0,0,0.04);
    border-radius: 3px;
}

.ember-list-view {
  overflow: auto;
  position: relative;
  width: 100%;
}
.ember-list-item-view {
  position: absolute;
}

.mobile-list .ember-list-view {
  -webkit-overflow-scrolling: touch;
  overflow-scrolling: touch;
}

.mobile-images-list .ember-list-view {
  background: white;
  -webkit-overflow-scrolling: touch;
  overflow-scrolling: touch;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  -o-user-select: none;
  user-select: none;
}

.mobile-large-images-list .photo-grid-list-item {
  width: 154px;
  height: 154px;
  float: left;
}

.mobile-large-images-list .medium-frame {
  background-color: white;
  padding: 4px;
  height: 133px;
  width: 133px;
  margin: 6px 7px 7px 6px;
  -webkit-box-shadow: rgba(0, 0, 0, 0.2) 2px 2px 2px;
  -moz-box-shadow: rgba(0, 0, 0, 0.2) 2px 2px 2px;
  box-shadow: rgba(0, 0, 0, 0.2) 2px 2px 2px;
}

.mobile-small-images-list .photo-grid-list-item {
  width: 154px;
  height: 154px;
  float: left;
}

.mobile-small-images-list .medium-frame {
  background-color: white;
  padding: 4px;
  height: 133px;
  width: 133px;
  margin: 6px 7px 7px 6px;
  -webkit-box-shadow: rgba(0, 0, 0, 0.2) 2px 2px 2px;
  -moz-box-shadow: rgba(0, 0, 0, 0.2) 2px 2px 2px;
  box-shadow: rgba(0, 0, 0, 0.2) 2px 2px 2px;
}

.multi-height .ember-list-item-view {
  width: 500px;
}

.multi-height .row {
  position: absolute;
  width: 500px;
}

.multi-height .dog {
  height: 50px;
  background-color: teal;
}
.multi-height .cat {
  height: 100px;
  background-color: pink;
}
.multi-height .other {
  height: 150px;
  background-color: purple;
}

.pull-to-refresh-list .ember-list-view {
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  -o-user-select: none;
  user-select: none;
}

.pull-to-refresh-list .ember-list-item-view {
  color: white;
  width: 100px;
  height: 100px;
}

.pull-to-refresh-list .ember-list-item-view img{
  width: 100px;
  height: 100px;
}

.pull-to-refresh-list .ember-list-item-view .text {
  position: absolute;
  top: 0;
  left: 0;
}

.pull-to-refresh-animation {
  background-color: yellow;
  height: 45px;
  left:0;
  right: 0;
  position: absolute;
  text-align: center;
  padding-top:30px;
}

.spinner {
  width: 30px;
  height: 30px;
  background-color: #333;

  margin: 100px auto;
  -webkit-animation: rotateplane 1.2s infinite ease-in-out;
  animation: rotateplane 1.2s infinite ease-in-out;
}

@-webkit-keyframes rotateplane {
  0% { -webkit-transform: perspective(120px) }
  50% { -webkit-transform: perspective(120px) rotateY(180deg) }
  100% { -webkit-transform: perspective(120px) rotateY(180deg)  rotateX(180deg) }
}

@keyframes rotateplane {
  0% {
    transform: perspective(120px) rotateX(0deg) rotateY(0deg);
    -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg)
  } 50% {
      transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
      -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg)
    } 100% {
        transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
        -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
      }
}

.pull-to-refresh-list .ember-list-item-view {
  background: black;
}

.pull-to-refresh-list .ember-list-container {
  -webkit-transform: translate3d(0px, 0px, 0);
}

.virtual .ember-list-view {
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  -o-user-select: none;
  user-select: none;
  border: 1px solid red;
}

.virtual .ember-list-item-view {
  position: absolute;
  color: steelblue;
  width: 100px;
  height: 100px;
}

.virtual .ember-list-item-view img{
  width: 100px;
  height: 100px;
}
.virtual .ember-list-item-view .text {
  position: absolute;
  top: 0;
  left: 0;
}

.virtual .ember-list-container {
  -webkit-transform: translate3d(0px, 0px, 0);
}

.list-item {
  box-sizing: border-box;
  width: 100%;
  height: 100%;
  border: 1px solid black;
}

================================================
FILE: tests/dummy/app/templates/application.hbs
================================================
<nav class="navbar navbar-inverse navbar-fixed-top">
    <div class="container">
    <div class="navbar-header">
        <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        </button>
        <LinkTo class="navbar-brand" @route="index">Ember Collection</LinkTo>
    </div>
    <div id="navbar" class="navbar-collapse collapse">
        <ul class="nav navbar-nav">
        <li><LinkTo @route="index">Home</LinkTo></li>
        <li><LinkTo @route="simple">Fixed Grid</LinkTo></li>
        <li><LinkTo  @route="mixed">Mixed Grid</LinkTo></li>
        <li><LinkTo @route="percentages">Percentages</LinkTo></li>
        <li><LinkTo @route="scroll-position">Scroll Position</LinkTo></li>
        </ul>
    </div>{{!--/.navbar-collapse --}}
    </div>
</nav>

<div class="container">
    {{outlet}}
</div>


================================================
FILE: tests/dummy/app/templates/components/.gitkeep
================================================


================================================
FILE: tests/dummy/app/templates/index.hbs
================================================
<div class="jumbotron">
    <h1>Ember Collection Demos</h1>
    <p>These are the demos for the <a href="https://github.com/adopted-ember-addons/ember-collection">ember-collection</a> addon. Their source code is within the the repo's <a href="https://github.com/adopted-ember-addons/ember-collection/tree/master/tests/dummy/app">tests/dummy application</a>.</p>
</div>

<div class="row">
    <div class="col-md-3">
        <h2>Fixed Grid Layout</h2>
        <p>Use the <code>fixed-grid-layout</code> when all items are the same size. The width and height of each item are bound as is the width and height of the container. Ember collection will re-layout items when any of these items change.</p>
        <p><LinkTo @route="simple" class="btn btn-default">Demo</LinkTo></p>
    </div>
    <div class="col-md-3">
        <h2>Mixed Grid Layout</h2>
        <p>Use the <code>mixed-grid-layout</code> when items can be a different size. The collection being itterated over is passed to the <code>mixed-grid-layout</code> helper. Each item in the collection must provide a <code>width</code> and <code>height</code> property of the item.</p>
        <p><LinkTo @route="mixed" class="btn btn-default">Demo</LinkTo></p>
    </div>
    <div class="col-md-3">
        <h2>Scroll Position</h2>
        <p>Use the <code>scroll-top</code> and <code>scroll-left</code> attributes to programtically scroll to a specific location in the collection. This can also be used to set an initial scroll position.</p>
        <p><LinkTo @route="scroll-position" class="btn btn-default">Demo</LinkTo></p>
    </div>
    <div class="col-md-3">
        <h2>Percentage Columns</h2>
        <p>Use the <code>percentage-columns-layout</code> to render a fixed number of columns that are sized using percentages. The <code>columns</code> paramter should be an array of integers that add to 100.</p>
        <p><LinkTo @route="percentages" class="btn btn-default">Demo</LinkTo></p>
    </div>
</div>
 


================================================
FILE: tests/dummy/app/templates/mixed.hbs
================================================
<div class="mixed" style="position:relative;height:500px">
  <EmberCollection @items={{this.model}} @estimated-height={{800}} @estimated-width={{500}} @buffer={{10}} @cell-layout={{mixed-grid-layout this.model}} as |item|>
    <div class="list-item">
      {{item.name}}
    </div>
  </EmberCollection>
</div>


================================================
FILE: tests/dummy/app/templates/percentages.hbs
================================================
<button {{on 'click' (fn this.changeColumn 1)}}>25-50-25</button>
<button {{on 'click' (fn this.changeColumn 2)}}>20-20-40-20</button>
<button {{on 'click' (fn this.changeColumn 3)}}>33-33-33</button>
<button {{on 'click' (fn this.changeColumn 4)}}>50-50</button>
<button {{on 'click' (fn this.changeColumn 5)}}>100</button>
<hr />
<div class="mixed" style="position:relative;height:500px;">
  <EmberCollection @items={{this.model}} @estimated-height={{800}} @estimated-width={{1000}} @buffer={{10}} @cell-layout={{percentage-columns-layout this.model.length this.columns 50}} as |item|>
    <div class="list-item">
      {{item.name}}
    </div>
  </EmberCollection>
</div>


================================================
FILE: tests/dummy/app/templates/scroll-position.hbs
================================================
<h3>Scroll Position</h3>
<button {{on 'click' this.makeSquare}}>Square</button>
<button {{on 'click' this.makeRow}}>Row</button>
<button {{on 'click' this.makeLongRect}}>Long Rectangle</button>
<button {{on 'click' this.makeTallRect}}>Tall Rectable</button>

{{!-- template-lint-disable require-input-label --}}
<p>
  Container Width: <input type='range' min=200 max=1000 value={{this.containerWidth}} oninput={{this.updateContainerWidth}}> {{this.containerWidth}}
  Container Height: <input type='range' min=200 max=1000 value={{this.containerHeight}} oninput={{this.updateContainerHeight}}> {{this.containerHeight}}
</p>
<p>
Item Height: {{this.itemHeight}}
Item Width: {{this.itemWidth}}
</p>
<p>
Scroll Left: <Input @value={{this.scrollLeft}} />
Scroll Top: <Input @value={{this.scrollTop}} />
</p>
<p>Note: The usage of this component remembers its scroll position. Try it by navigating away from this route and then returning.</p>
<hr />

<div class="simple-list" style={{{concat 'position:relative;width:' this.containerWidth 'px;height:' this.containerHeight 'px;'}}}>
  <EmberCollection @items={{this.model}} @estimated-height={{this.containerHeight}} @estimated-width={{this.containerWidth}} @buffer={{10}} @cell-layout={{fixed-grid-layout this.itemWidth this.itemHeight}} @scroll-left={{this.scrollLeft}} @scroll-top={{this.scrollTop}} @scroll-change={{this.scrollChange}} as |item|>
    <div class="list-item">
      {{item.name}}
    </div>
  </EmberCollection>
</div>


================================================
FILE: tests/dummy/app/templates/simple.hbs
================================================
<h3>Simple</h3>
<button {{on 'click' this.makeSquare}}>Square</button>
<button {{on 'click' this.makeRow}}>Row</button>
<button {{on 'click' this.makeLongRect}}>Long Rectangle</button>
<button {{on 'click' this.makeTallRect}}>Tall Rectable</button>
<button {{on 'click' this.shuffle}}>Shuffle</button>
<p>
  {{!-- template-lint-disable require-input-label --}}
  Container Width: <input type='range' min=200 max=1000 value={{this.containerWidth}} oninput={{this.updateContainerWidth}}> {{this.containerWidth}}
  Container Height: <input type='range' min=200 max=1000 value={{this.containerHeight}} oninput={{this.updateContainerHeight}}> {{this.containerHeight}}
</p>
<p>
Item Height: {{this.itemHeight}}
Item Width: {{this.itemWidth}}
</p>
<hr />

<div class="simple-list" style={{{concat 'position:relative;width:' this.containerWidth 'px;height:' this.containerHeight 'px;'}}}>
  <EmberCollection @items={{this.model}} @buffer={{10}} @cell-layout={{fixed-grid-layout this.itemWidth this.itemHeight}} as |item|>
    <div class="list-item">
      {{item.name}}
    </div>
  </EmberCollection>
</div>


================================================
FILE: tests/dummy/app/utils/fixtures.js
================================================
export var types = [
  {id:  1, type: "cat",   name: "Andrew"},
  {id:  2, type: "cat",   name: "Andrew"},
  {id:  3, type: "cat",   name: "Bruce"},
  {id:  4, type: "other", name: "Xbar"},
  {id:  5, type: "dog",   name: "Caroline"},
  {id:  6, type: "cat",   name: "David"},
  {id:  7, type: "other", name: "Xbar"},
  {id:  8, type: "other", name: "Xbar"},
  {id:  9, type: "dog",   name: "Edward"},
  {id: 10, type: "dog",   name: "Francis"},
  {id: 11, type: "dog",   name: "George"},
  {id: 12, type: "other", name: "Xbar"},
  {id: 13, type: "dog",   name: "Harry"},
  {id: 14, type: "cat",   name: "Ingrid"},
  {id: 15, type: "other", name: "Xbar"},
  {id: 16, type: "cat",   name: "Jenn"},
  {id: 17, type: "cat",   name: "Kelly"},
  {id: 18, type: "other", name: "Xbar"},
  {id: 19, type: "other", name: "Xbar"},
  {id: 20, type: "cat",   name: "Larry"},
  {id: 21, type: "other", name: "Xbar"},
  {id: 22, type: "cat",   name: "Manny"},
  {id: 23, type: "dog",   name: "Nathan"},
  {id: 24, type: "cat",   name: "Ophelia"},
  {id: 25, type: "dog",   name: "Patrick"},
  {id: 26, type: "other", name: "Xbar"},
  {id: 27, type: "other", name: "Xbar"},
  {id: 28, type: "other", name: "Xbar"},
  {id: 29, type: "other", name: "Xbar"},
  {id: 30, type: "other", name: "Xbar"},
  {id: 31, type: "cat",   name: "Quincy"},
  {id: 32, type: "dog",   name: "Roger"},
  ];


================================================
FILE: tests/dummy/app/utils/images.js
================================================
var images = [
  'images/ebryn.jpg',
  'images/iterzic.jpg',
  'images/kselden.jpg',
  'images/machty.jpg',
  'images/rwjblue.jpg',
  'images/stefanpenner.jpg',
  'images/tomdale.jpg',
  'images/trek.jpg',
  'images/wagenet.jpg',
  'images/wycats.jpg'
];

var smallImages = [
  'images/small/Ba_Gua_Feng-Shui-Mirror.gif',
  'images/small/Bonsai.gif',
  'images/small/Chouchin_Reinensai_Lantern.gif',
  'images/small/Chouchin_Kuroshiro_Lantern_.gif',
  'images/small/Chouchin_Shinku_Lantern.gif',
  'images/small/Fuurin_Glass_Wind_Chime.gif',
  'images/small/Geta_Wooden_Sandal_.gif',
  'images/small/Gunsen_Fan_.gif',
  'images/small/iChing_Kouka_Heads-Coin.gif',
  'images/small/iChing_Kouka_Tails_Coin.gif',
  'images/small/Ishidourou_Snow_Lantern.gif',
  'images/small/Kakejiku_Hanging_Scroll.gif',
  'images/small/Katana_and_Sheath.gif',
  'images/small/Kimono_Buru_Blue.gif',
  'images/small/Kimono_Chairo_Tan.gif',
  'images/small/Koi.gif',
  'images/small/Shamisen.gif',
  'images/small/Shodou_Calligraphy.gif',
  'images/small/Torii.gif',
  'images/small/Tsukubai_Water_Basin.gif'
];

var strangeRatios = [
  'images/strange-ratios/horizontal-rectangle.jpg',
  'images/strange-ratios/square.jpg',
  'images/strange-ratios/vertical-rectangle.jpg'
];

export default { images, smallImages, strangeRatios };


================================================
FILE: tests/dummy/app/utils/make-model.js
================================================
import images from './images';

export default function makeModel(count = 1000, imageArrayName = 'images') {
  var imagesArray = images[imageArrayName];
  return function model() {
    var result = [];
    for (var i = 0; i < count; i++) {
      result.push({
        name: `Item ${i+1}`,
        imageSrc: imagesArray[i%imagesArray.length]
      });
    }
    return result;
  };
}


================================================
FILE: tests/dummy/config/ember-cli-update.json
================================================
{
  "schemaVersion": 0,
  "packages": [
    {
      "name": "ember-cli",
      "version": "3.12.0",
      "blueprints": [
        {
          "name": "addon",
          "outputRepo": "https://github.com/ember-cli/ember-addon-output",
          "codemodsSource": "ember-addon-codemods-manifest@1",
          "isBaseBlueprint": true,
          "options": [
            "--yarn",
            "--no-welcome"
          ]
        }
      ]
    }
  ]
}


================================================
FILE: tests/dummy/config/environment.js
================================================
'use strict';

module.exports = function(environment) {
  let ENV = {
    modulePrefix: 'dummy',
    environment,
    rootURL: '/',
    locationType: 'history',
    EmberENV: {
      FEATURES: {
        // Here you can enable experimental features on an ember canary build
        // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true
      },
      EXTEND_PROTOTYPES: {
        // Prevent Ember Data from overriding Date.parse.
        Date: false
      }
    },

    APP: {
      // Here you can pass flags/options to your application instance
      // when it is created
    }
  };

  if (environment === 'development') {
    // ENV.APP.LOG_RESOLVER = true;
    // ENV.APP.LOG_ACTIVE_GENERATION = true;
    // ENV.APP.LOG_TRANSITIONS = true;
    // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
    // ENV.APP.LOG_VIEW_LOOKUPS = true;
  }

  if (environment === 'test') {
    // Testem prefers this...
    ENV.locationType = 'none';

    // keep test console output quieter
    ENV.APP.LOG_ACTIVE_GENERATION = false;
    ENV.APP.LOG_VIEW_LOOKUPS = false;

    ENV.APP.rootElement = '#ember-testing';
    ENV.APP.autoboot = false;
  }

  if (environment === 'production') {
    // here you can enable a production-specific feature
  }

  return ENV;
};


================================================
FILE: tests/dummy/config/optional-features.json
================================================
{
  "application-template-wrapper": false,
  "default-async-observers": true,
  "jquery-integration": false,
  "template-only-glimmer-components": true
}


================================================
FILE: tests/dummy/config/targets.js
================================================
'use strict';

const browsers = [
  'last 1 Chrome versions',
  'last 1 Firefox versions',
  'last 1 Safari versions'
];

const isCI = !!process.env.CI;
const isProduction = process.env.EMBER_ENV === 'production';

if (isCI || isProduction) {
  browsers.push('ie 11');
}

module.exports = {
  browsers
};


================================================
FILE: tests/dummy/public/crossdomain.xml
================================================
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
  <!-- Read this: www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html -->

  <!-- Most restrictive policy: -->
  <site-control permitted-cross-domain-policies="none"/>

  <!-- Least restrictive policy: -->
  <!--
  <site-control permitted-cross-domain-policies="all"/>
  <allow-access-from domain="*" to-ports="*" secure="false"/>
  <allow-http-request-headers-from domain="*" headers="*" secure="false"/>
  -->
</cross-domain-policy>


================================================
FILE: tests/dummy/public/robots.txt
================================================
# http://www.robotstxt.org
User-agent: *
Disallow:


================================================
FILE: tests/helpers/destroy-app.js
================================================
import { run } from '@ember/runloop';

export default function destroyApp(application) {
  run(application, 'destroy');
}


================================================
FILE: tests/helpers/helpers.js
================================================
import { A } from '@ember/array';
import { get } from '@ember/object';
import Ember from 'ember';
var compile = Ember.Handlebars.compile;

function generateContent(n) {
  var content = A([]);
  for (var i = 0; i < n; i++) {
    content.push({name: "Item " + (i+1)});
  }
  return content;
}

function findScrollable(context) {
  return context.querySelector('.ember-collection > div:first-child'); // scrollable's element
}

function findContainer(context) {
  return context.querySelector('.ember-collection > div:first-child > div:first-child'); // scrollable's content element
}

function findItems(context) {
  return Array.prototype.slice.call(
    context.querySelectorAll('.ember-collection > div:first-child > div:first-child > div')  // scrollable's content's children (cells)
  );
}

function findVisibleItems(context) {
  let items = Array.prototype.slice.call(
    context.querySelectorAll('.ember-collection > div:first-child > div:first-child > div')
  )
  return items.filter(item => {
    let style = getComputedStyle(item);
    return style.display !== 'none' && style.visibility === 'visible';
  });
}

function extractPosition(element) {
    let parentRect = element.parentElement.getBoundingClientRect();
    let elementRect = element.getBoundingClientRect();
    if (elementRect.width > 0 && elementRect.height > 0) {
      return {
          left: elementRect.left - parentRect.left,
          top: elementRect.top - parentRect.top,
          width: elementRect.width,
          height: elementRect.height
      };
    }
    return null;
}

function sortItemsByPosition(view, visibleOnly) {
  var find = visibleOnly ? findVisibleItems : findItems;
  var items = find(view);
  return sortElementsByPosition(items);
}

function sortElementsByPosition (elements) {
  return elements
    .filter(elem => extractPosition(elem))
    .sort(function(a, b) {
      return sortByPosition(extractPosition(a), extractPosition(b));
    });
}

function sortByPosition(a, b) {
  if (b.top === a.top){
    return (a.left - b.left);
  }
  return (a.top - b.top);
}

function itemPositions(view) {
  return A(findItems(view)).toArray().map(function(e) {
    return extractPosition(e);
  }).sort(sortByPosition);
}

function checkContent(view, assert, expectedFirstItem, expectedCount) {
  var elements = sortItemsByPosition(view.element, true);
  var content = A(view.get('content') || []);
  assert.ok(
    expectedFirstItem + expectedCount <= get(content, 'length'),
    'No more items than are in content are rendered.');
  var buffer = view.get('buffer') === undefined ? 5 : view.get('buffer');

  // TODO: we are recapitulating calculations done by fixed grid, as
  // we don't have access to the layout. This will not work with
  // mixed grid layout.
  //
  // In the future, if a listener for actual first item and count are
  // included in interface, we can limit ourselves to just recomputing
  // the number that should be in the buffer.

  var width = view.get('width') | 0;
  var itemWidth = view.get('itemWidth') || 1;
  var istart = Math.max(expectedFirstItem - buffer, 0);
  // TODO: padding is one extra row -- how to calculate with mixed grid
  var padding = Math.floor(width / itemWidth);
  // include buffer before
  var scount = expectedCount + Math.min(expectedFirstItem, buffer);
  // include padding (in case of non-integral scroll)
  var numItems = get(content, 'length');
  var pcount = scount + Math.min(Math.max(numItems - istart - scount, 0), padding);
  // include buffer after
  var count = pcount + Math.min(Math.max(numItems - istart - pcount, 0), buffer);
  assert.equal(
    elements.length, count, "Rendered expected number of elements.");
  for (let i = 0; i < count; i++) {
    let elt = elements[i];
    let item = content.objectAt(i + istart);
    assert.dom(elt).hasText(item.name, 'Item ' + (i + 1) + ' rendered');
  }
}

export {
  itemPositions,
  generateContent,
  extractPosition,
  compile,
  findContainer,
  findScrollable,
  findItems,
  findVisibleItems,
  checkContent,
  sortItemsByPosition };


================================================
FILE: tests/helpers/module-for-acceptance.js
================================================
import { module } from 'qunit';
import { resolve } from 'rsvp';
import startApp from '../helpers/start-app';
import destroyApp from '../helpers/destroy-app';

export default function(name, options = {}) {
  module(name, {
    beforeEach() {
      this.application = startApp();

      if (options.beforeEach) {
        return options.beforeEach.apply(this, arguments);
      }
    },

    afterEach() {
      let afterEach = options.afterEach && options.afterEach.apply(this, arguments);
      return resolve(afterEach).then(() => destroyApp(this.application));
    }
  });
}


================================================
FILE: tests/helpers/module-for-view.js
================================================
import { deprecate } from '@ember/application/deprecations';
import { tryInvoke } from '@ember/utils';
import { run } from '@ember/runloop';
import { merge } from '@ember/polyfills';
import Ember from 'ember';
import TestModule from 'ember-test-helpers/test-module';
import { getResolver } from 'ember-test-helpers/test-resolver';
import { createModule } from 'ember-qunit/qunit-module';

var TestModuleForView = TestModule.extend({
  init: function(viewName, description, callbacks) {
    this.viewName = viewName;
    this._super.call(this, 'component:' + viewName, description, callbacks);
    this.setupSteps.push(this.setupView);
  },
  initNeeds: function() {
    this.needs = [];
    // toplevel refers to class extended from Ember.View
    if (this.subjectName !== 'component:toplevel') {
      this.needs.push(this.subjectName);
    }
    if (this.callbacks.needs) {
      this.needs = this.needs.concat(this.callbacks.needs);
      delete this.callbacks.needs;
    }
  },
  setupView: function() {
    var _this = this;
    var resolver = getResolver();
    var container = this.container;
    var context = this.context;
    var templateName = 'template:' + this.viewName;
    var template = resolver.resolve(templateName);
    if (template) {
      container.register(templateName, template);
      container.injection(this.subjectName, 'template', templateName);
    }
    context.dispatcher = Ember.EventDispatcher.create();
    context.dispatcher.setup({}, '#ember-testing');
    this.callbacks.render = function(options) {
      var containerView = Ember.ContainerView.create(merge({container: container}, options));
      var view = run(function(){
        var subject = context.subject();
        containerView.pushObject(subject);
        containerView.appendTo('#ember-testing');
        return subject;
      });
      _this.teardownSteps.unshift(function() {
        run(function() {
          tryInvoke(containerView, 'destroy');
        });
      });
      return view.$();
    };
    this.callbacks.append = function() {
      deprecate('this.append() is deprecated. Please use this.render() instead.');
      return this.callbacks.render();
    };
    context.$ = function() {
      var $view = this.render();
      var subject = this.subject();
      if (arguments.length){
        return subject.$.apply(subject, arguments);
      } else {
        return $view;
      }
    };
  },
  defaultSubject: function(options, factory) {
    return factory.create(options);
  }
});

export default function moduleForView(name, description, callbacks) {
  createModule(TestModuleForView, name, description, callbacks);
}


================================================
FILE: tests/helpers/start-app.js
================================================
import Application from '../../app';
import config from '../../config/environment';
import { merge } from '@ember/polyfills';
import { run } from '@ember/runloop';

export default function startApp(attrs) {
  let attributes = merge({}, config.APP);
  attributes.autoboot = true;
  attributes = merge(attributes, attrs); // use defaults, but you can override;

  return run(() => {
    let application = Application.create(attributes);
    application.setupForTesting();
    application.injectTestHelpers();
    return application;
  });
}


================================================
FILE: tests/index.html
================================================
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Dummy Tests</title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    {{content-for "head"}}
    {{content-for "test-head"}}

    <link rel="stylesheet" href="{{rootURL}}assets/vendor.css">
    <link rel="stylesheet" href="{{rootURL}}assets/dummy.css">
    <link rel="stylesheet" href="{{rootURL}}assets/test-support.css">

    <style type="text/css">
     #ember-testing {
         zoom: 1;
         transform: none;
     }
    </style>
    {{content-for "head-footer"}}
    {{content-for "test-head-footer"}}
  </head>
  <body>
    {{content-for "body"}}
    {{content-for "test-body"}}

    <div id="qunit"></div>
    <div id="qunit-fixture">
      <div id="ember-testing-container">
        <div id="ember-testing"></div>
      </div>
    </div>

    <script src="/testem.js" integrity=""></script>
    <script src="{{rootURL}}assets/vendor.js"></script>
    <script src="{{rootURL}}assets/test-support.js"></script>
    <script src="{{rootURL}}assets/dummy.js"></script>
    <script src="{{rootURL}}assets/tests.js"></script>

    {{content-for "body-footer"}}
    {{content-for "test-body-footer"}}
  </body>
</html>


================================================
FILE: tests/templates/fixed-grid.js
================================================
import { hbs } from 'ember-cli-htmlbars';

export default hbs`  {{!-- template-lint-disable no-curly-component-invocation --}}
  <div style={{size-to-style this.width this.height}}>{{#ember-collection
    items=this.content
    cell-layout=(fixed-grid-layout this.itemWidth this.itemHeight)
    estimated-width=this.width
    estimated-height=this.height
    scroll-left=this.offsetX
    scroll-top=this.offsetY
    buffer=this.buffer
    class="ember-collection"
    as |item| ~}}
  <div class="list-item">{{item.name}}</div>
{{~/ember-collection~}}</div>`;


================================================
FILE: tests/templates/indexed.js
================================================
import { hbs } from 'ember-cli-htmlbars';

export default hbs`  {{!-- template-lint-disable no-curly-component-invocation --}}
  <div style={{size-to-style this.width this.height}}>{{#ember-collection
    items=this.content
    cell-layout=(fixed-grid-layout this.itemWidth this.itemHeight)
    estimated-width=this.width
    estimated-height=this.height
    scroll-left=this.offsetX
    scroll-top=this.offsetY
    buffer=this.buffer
    class="ember-collection"
    as |item index| ~}}
  <div class="list-item">{{index}}:</div>
{{~/ember-collection~}}</div>`;


================================================
FILE: tests/templates/percentage.js
================================================
import { hbs } from 'ember-cli-htmlbars';

export default hbs`  {{!-- template-lint-disable no-curly-component-invocation --}}
  <div style={{size-to-style this.width this.height}}>{{#ember-collection
    items=this.content
    cell-layout=(percentage-columns-layout this.content.length this.columns this.itemHeight)
    estimated-width=this.width
    estimated-height=this.height
    scroll-left=this.offsetX
    scroll-top=this.offsetY
    buffer=this.buffer
    class="ember-collection"
    as |item| ~}}
  <div class="list-item">{{item.name}}</div>
{{~/ember-collection~}}</div>`;


================================================
FILE: tests/test-helper.js
================================================
/* global QUnit */
import Application from '../app';
import config from '../config/environment';
import { setApplication } from '@ember/test-helpers';
import { start } from 'ember-qunit';
import { setup } from 'qunit-dom';

setup(QUnit.assert);
setApplication(Application.create(config.APP));

start();


================================================
FILE: tests/unit/.gitkeep
================================================


================================================
FILE: tests/unit/content-test.js
================================================
import ArrayProxy from '@ember/array/proxy';
import { A } from '@ember/array';
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, settled } from '@ember/test-helpers';
import {
  generateContent, sortItemsByPosition, findItems, findVisibleItems, findContainer,
  checkContent } from '../helpers/helpers';
import fixedGridTemplate from '../templates/fixed-grid';
import indexedTemplate from '../templates/indexed';


var nItems = 100;
var itemWidth = 100;
var itemHeight = 40;
var width = 500;
var renderedWidth = 520; // adjusted for scrollbar
var height = 400;

module('manipulate content', function(hooks) {
  setupRenderingTest(hooks);

  test("replacing the list content", async function(assert) {
    var content = generateContent(nItems);

    this.setProperties({height, width: renderedWidth, itemHeight, itemWidth, content});
    await render(fixedGridTemplate);
    this.set('content', A([{name: 'The only item'}]));
    await settled();

    assert.equal(findItems(this.element)
      .filter(function(elem) { return getComputedStyle(elem).display !== 'none'; })
      .length, 1, "The rendered list was updated");

    assert.equal(
      findItems(this.element)[0].getBoundingClientRect().height,
      itemHeight,
      "The items have the correct height");
    checkContent(this, assert, 0, 1);
  });

  test("adding to the front of the list content", async function(assert) {
    var content = generateContent(nItems);

    this.setProperties({height, width: renderedWidth, itemHeight, itemWidth, content});
    await render(fixedGridTemplate);

    content.unshiftObject({name: "Item -1"});
    await settled();

    var positionSorted = sortItemsByPosition(this.element);

    assert.dom(positionSorted[0])
      .hasTextContaining("Item -1", "The item has been inserted in the list");

    var expectedRows = Math.ceil((nItems + 1) / (width / itemWidth));

    assert.equal(
      findContainer(this.element).getBoundingClientRect().height,
      expectedRows * itemHeight,
      "The scrollable view has the correct height");
    checkContent(this, assert, 0, 50);
  });

  test("inserting in the middle of visible content", async function(assert) {
    var content = generateContent(nItems);

    this.setProperties({height, width: renderedWidth, itemHeight, itemWidth, content});
    await render(fixedGridTemplate);

    content.insertAt(2, {name: "Item 2'"});
    await settled();

    var positionSorted = sortItemsByPosition(this.element);
    assert.dom(positionSorted[0])
      .hasTextContaining("Item 1", "The item has been inserted in the list");

    assert.dom(positionSorted[2])
      .hasTextContaining("Item 2'", "The item has been inserted in the list");

    checkContent(this, assert, 0, 50);
  });

  test("clearing the content", async function(assert) {
    var content = generateContent(nItems);

    this.setProperties({height, width: renderedWidth, itemHeight, itemWidth, content});
    await render(fixedGridTemplate);

    content.clear();
    await settled();

    assert.equal(findItems(this.element)
      .filter(function(elem) { return getComputedStyle(elem).display !== 'none'; })
      .length, 0, "The rendered list does not contain any elements.");
  });

  test("deleting the first element", async function(assert) {
    var content = generateContent(nItems);

    this.setProperties({height, width: renderedWidth, itemHeight, itemWidth, content});
    await render(fixedGridTemplate);

    var positionSorted = sortItemsByPosition(this.element);

    assert.dom(positionSorted[0])
      .hasTextContaining("Item 1", "Item 1 has not been removed from the list.");

    content.removeAt(0);
    await settled();

    positionSorted = sortItemsByPosition(this.element);

    assert.dom(positionSorted[0])
      .hasTextContaining("Item 2", "Item 1 has been remove from the list.");
      checkContent(this, assert, 0, 50);
  });

  test("working with an ArrayProxy", async function(assert) {
    var content = ArrayProxy.create({content: A(generateContent(nItems)) });

    this.setProperties({height, width: renderedWidth, itemHeight, itemWidth, content});
    await render(fixedGridTemplate);

    assert.equal(findItems(this.element)
      .filter(function(elem) { return getComputedStyle(elem).display !== 'none'; })
      .length, 60, "The rendered list was updated");

    assert.equal(
      findItems(this.element)[0].getBoundingClientRect().height,
      itemHeight,
      "The items have the correct height");
    checkContent(this, assert, 0, 50);
  });

  test("indexes update correctly", async function(assert) {
    var content = generateContent(30);
    var filterIndexes  = [];

    this.setProperties({height, width, itemHeight, itemWidth, content, filterIndexes});
    await render(indexedTemplate);

    this.set('content', [content[1], content[3], content[7], content[13]]);
    await settled();

    function joinContent(context) {
      return findVisibleItems(context).map(item => item.textContent).join('').split(':').sort().join(':');
    }

    assert.equal(joinContent(this.element), ":0:1:2:3", "The indexes updated correctly");

    this.set('content', [content[1], content[3], content[7], content[13], content[27]]);
    await settled();

    assert.equal(joinContent(this.element), ":0:1:2:3:4", "The indexes updated correctly");
  });
});



================================================
FILE: tests/unit/fixed-grid-test.js
================================================
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import {
  generateContent,
  sortItemsByPosition
} from '../helpers/helpers';
import template from '../templates/fixed-grid';

module('display in fixed grid', function(hooks) {
  setupRenderingTest(hooks);

  test('display 5 in 6', async function(assert) {
    var width = 150, height = 500, itemWidth = 50, itemHeight = 50;
    var offsetY = 100;
    var content = generateContent(5);

    this.setProperties({ width, height, itemWidth, itemHeight, content, offsetY });
    await render(template);
    var positionSorted = sortItemsByPosition(this.element);

    assert.dom(positionSorted[0])
      .hasTextContaining("Item 1", "The first item has not been hidden");
  });
});


================================================
FILE: tests/unit/layout-test.js
================================================
import { hbs } from 'ember-cli-htmlbars';
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { generateContent } from '../helpers/helpers';

var nItems = 5;
var itemWidth = 100;
var itemHeight = 40;
var width = 500;
var height = 400;
var columns = [25, 50, 15, 10];

module('Basic layout tests', function(hooks) {
  setupRenderingTest(hooks);

  test("ember-collection calls formatItemStyle", async function(assert) {
    var content = generateContent(nItems);
    var callCount = 0;
    var fakeLayout = {
        indexAt: function() { return 0; },
        count: function() { return nItems; },
        contentSize: function() {
            return {width, height};
        },
        formatItemStyle: function() {
            callCount++;
        }
    };

    var template = hbs`
      <div style={{size-to-style this.width this.height}}>
        <EmberCollection @items={{this.content}} @cell-layout={{this.fakeLayout}} @estimated-width={{this.width}} @estimated-height={{this.height}} @scroll-left={{this.offsetX}} @scroll-top={{this.offsetY}} @buffer={{this.buffer}} @class="ember-collection" as |item|>
          <div class="list-item">{{item.name}}</div>
        </EmberCollection>
      </div>`;

    this.setProperties({height, width, itemHeight, itemWidth, content, columns, fakeLayout});
    await render(template);

    assert.equal(callCount, nItems, 'formatItemStyle is called for each rendered item');
  });
});


================================================
FILE: tests/unit/multi-height-list-view-test.js
================================================
import { get } from '@ember/object';
import { A } from '@ember/array';
import { run } from '@ember/runloop';
import { setupRenderingTest } from 'ember-qunit';
import '@ember/test-helpers';
import { module, skip } from 'qunit';
import { sortItemsByPosition, findItems } from '../helpers/helpers';
// import { hbs } from 'ember-cli-htmlbars';

// TODO: Remove these declarations. They're just there to keep JSHint happy.
let compile, itemPositions, ListItemView, ReusableListItemView;

module('multi-height', function(hooks) {
  setupRenderingTest(hooks);

  skip("Correct height based on content", function(assert) {
    var content = [
      { id:  1, type: "cat",   height: 100, name: "Andrew" },
      { id:  3, type: "cat",   height: 100, name: "Bruce" },
      { id:  4, type: "other", height: 150, name: "Xbar" },
      { id:  5, type: "dog",   height:  50, name: "Caroline" },
      { id:  6, type: "cat",   height: 100, name: "David" },
      { id:  7, type: "other", height: 150, name: "Xbar" },
      { id:  8, type: "other", height: 150, name: "Xbar" },
      { id:  9, type: "dog",   height:  50, name: "Edward" },
      { id: 10, type: "dog",   height:  50, name: "Francis" },
      { id: 11, type: "dog",   height:  50, name: "George" },
      { id: 12, type: "other", height: 150, name: "Xbar" },
      { id: 13, type: "dog",   height:  50, name: "Harry" },
      { id: 14, type: "cat",   height: 100, name: "Ingrid" },
      { id: 15, type: "other", height: 150, name: "Xbar" },
      { id: 16, type: "cat",   height: 100, name: "Jenn" },
      { id: 17, type: "cat",   height: 100, name: "Kelly" },
      { id: 18, type: "other", height: 150, name: "Xbar" },
      { id: 19, type: "other", height: 150, name: "Xbar" },
      { id: 20, type: "cat",   height: 100, name: "Larry" },
      { id: 21, type: "other", height: 150, name: "Xbar" },
      { id: 22, type: "cat",   height: 100, name: "Manny" },
      { id: 23, type: "dog",   height:  50, name: "Nathan" },
      { id: 24, type: "cat",   height: 100, name: "Ophelia" },
      { id: 25, type: "dog",   height:  50, name: "Patrick" },
      { id: 26, type: "other", height: 150, name: "Xbar" },
      { id: 27, type: "other", height: 150, name: "Xbar" },
      { id: 28, type: "other", height: 150, name: "Xbar" },
      { id: 29, type: "other", height: 150, name: "Xbar" },
      { id: 30, type: "other", height: 150, name: "Xbar" },
      { id: 31, type: "cat",   height: 100, name: "Quincy" },
      { id: 32, type: "dog",   height:  50, name: "Roger" },
    ];

    var view;
    run(this, function(){
      view = this.subject({
        content: A(content),
        height: 300,
        width: 500,
        rowHeight: 100,
        itemViews: {
          cat: ListItemView.extend({
            template: compile("Meow says {{name}} expected: cat === {{type}} {{id}}")
          }),
          dog: ListItemView.extend({
            template: compile("Woof says {{name}} expected: dog === {{type}} {{id}}")
          }),
          other: ListItemView.extend({
            template: compile("Potato says {{name}} expected: other === {{type}} {{id}}")
          })
        },
        itemViewForIndex: function(idx) {
          return this.itemViews[A(this.get('content')).objectAt(idx).type];
        },
        heightForIndex: function(idx) {
          return get(A(this.get('content')).objectAt(idx), 'height');
        }
      });
    });

    this.render();

    assert.equal(view.get('totalHeight'), 3350);

    var positionSorted = sortItemsByPosition(this);
    assert.equal(findItems(this).length, 4);

    assert.dom(positionSorted[0]).hasText("Meow says Andrew expected: cat === cat 1");
    assert.dom(positionSorted[1]).hasText("Meow says Bruce expected: cat === cat 3");
    assert.dom(positionSorted[2]).hasText("Potato says Xbar expected: other === other 4");
    assert.dom(positionSorted[3]).hasText("Woof says Caroline expected: dog === dog 5");

    assert.deepEqual(itemPositions(view), [
      { x:0, y:    0 }, // <-- in view
      { x:0, y:  100 }, // <-- in view
      { x:0, y:  200 }, // <-- in view
      { x:0, y:  350 }  // <-- buffer
    ], 'went beyond scroll max via overscroll');

    run(view, 'scrollTo', 1000);
    positionSorted = sortItemsByPosition(this);

    assert.dom(positionSorted[0]).hasText("Potato says Xbar expected: other === other 12");
    assert.dom(positionSorted[1]).hasText("Woof says Harry expected: dog === dog 13");
    assert.dom(positionSorted[2]).hasText("Meow says Ingrid expected: cat === cat 14");
    assert.dom(positionSorted[3]).hasText("Potato says Xbar expected: other === other 15");

    assert.deepEqual(itemPositions(view), [
      { x:0, y: 950 }, // <-- partially in view
      { x:0, y: 1100 }, // <-- in view
      { x:0, y: 1150 }, // <-- in view
      { x:0, y: 1250 }  // <-- partially in view
    ], 'went beyond scroll max via overscroll');
  });

  skip("Correct height based on view", function(assert) {
    var content = [
      { id:  1, type: "cat",   name: "Andrew" },
      { id:  3, type: "cat",   name: "Bruce" },
      { id:  4, type: "other", name: "Xbar" },
      { id:  5, type: "dog",   name: "Caroline" },
      { id:  6, type: "cat",   name: "David" },
      { id:  7, type: "other", name: "Xbar" },
      { id:  8, type: "other", name: "Xbar" },
      { id:  9, type: "dog",   name: "Edward" },
      { id: 10, type: "dog",   name: "Francis" },
      { id: 11, type: "dog",   name: "George" },
      { id: 12, type: "other", name: "Xbar" },
      { id: 13, type: "dog",   name: "Harry" },
      { id: 14, type: "cat",   name: "Ingrid" },
      { id: 15, type: "other", name: "Xbar" },
      { id: 16, type: "cat",   name: "Jenn" },
      { id: 17, type: "cat",   name: "Kelly" },
      { id: 18, type: "other", name: "Xbar" },
      { id: 19, type: "other", name: "Xbar" },
      { id: 20, type: "cat",   name: "Larry" },
      { id: 21, type: "other", name: "Xbar" },
      { id: 22, type: "cat",   name: "Manny" },
      { id: 23, type: "dog",   name: "Nathan" },
      { id: 24, type: "cat",   name: "Ophelia" },
      { id: 25, type: "dog",   name: "Patrick" },
      { id: 26, type: "other", name: "Xbar" },
      { id: 27, type: "other", name: "Xbar" },
      { id: 28, type: "other", name: "Xbar" },
      { id: 29, type: "other", name: "Xbar" },
      { id: 30, type: "other", name: "Xbar" },
      { id: 31, type: "cat",   name: "Quincy" },
      { id: 32, type: "dog",   name: "Roger" },
    ];

    var view;
    run(this, function(){
      view = this.subject({
        content: A(content),
        height: 300,
        width: 500,
        rowHeight: 100,
        itemViews: {
          cat: ListItemView.extend({
            rowHeight: 100,
            template: compile("Meow says {{name}} expected: cat === {{type}} {{id}}")
          }),
          dog: ListItemView.extend({
            rowHeight: 50,
            template: compile("Woof says {{name}} expected: dog === {{type}} {{id}}")
          }),
          other: ListItemView.extend({
            rowHeight: 150,
            template: compile("Potato says {{name}} expected: other === {{type}} {{id}}")
          })
        },
        itemViewForIndex: function(idx){
          return this.itemViews[get(A(this.get('content')).objectAt(idx), 'type')];
        },
        heightForIndex: function(idx) {
          // proto() is a quick hack, lets just store this on the class..
          return this.itemViewForIndex(idx).proto().rowHeight;
        }
      });
    });

    this.render();

    assert.equal(view.get('totalHeight'), 3350);

    var positionSorted = sortItemsByPosition(this);
    assert.equal(findItems(this).length, 4);

    assert.dom(positionSorted[0]).hasText("Meow says Andrew expected: cat === cat 1");
    assert.dom(positionSorted[1]).hasText("Meow says Bruce expected: cat === cat 3");
    assert.dom(positionSorted[2]).hasText("Potato says Xbar expected: other === other 4");
    assert.dom(positionSorted[3]).hasText("Woof says Caroline expected: dog === dog 5");

    assert.deepEqual(itemPositions(view), [
      { x:0, y:    0 }, // <-- in view
      { x:0, y:  100 }, // <-- in view
      { x:0, y:  200 }, // <-- in view
      { x:0, y:  350 }  // <-- buffer
    ], 'went beyond scroll max via overscroll');

    run(view, 'scrollTo', 1000);
    positionSorted = sortItemsByPosition(this);

    assert.dom(positionSorted[0]).hasText("Potato says Xbar expected: other === other 12");
    assert.dom(positionSorted[1]).hasText("Woof says Harry expected: dog === dog 13");
    assert.dom(positionSorted[2]).hasText("Meow says Ingrid expected: cat === cat 14");
    assert.dom(positionSorted[3]).hasText("Potato says Xbar expected: other === other 15");

    assert.deepEqual(itemPositions(view), [
      { x:0, y:  950 }, // <-- partially in view
      { x:0, y: 1100 }, // <-- in view
      { x:0, y: 1150 }, // <-- in view
      { x:0, y: 1250 }  // <-- partially in view
    ], 'went beyond scroll max via overscroll');
  });

  skip("handle bindable rowHeight with multi-height (only fallback case)", function(assert) {
    var content = [
      { id:  1, type: "cat",   name: "Andrew" },
      { id:  3, type: "cat",   name: "Bruce" },
      { id:  4, type: "other", name: "Xbar" },
      { id:  5, type: "dog",   name: "Caroline" },
      { id:  6, type: "cat",   name: "David" },
      { id:  7, type: "other", name: "Xbar" },
      { id:  8, type: "other", name: "Xbar" },
      { id:  9, type: "dog",   name: "Edward" },
      { id: 10, type: "dog",   name: "Francis" },
      { id: 11, type: "dog",   name: "George" },
      { id: 12, type: "other", name: "Xbar" },
      { id: 13, type: "dog",   name: "Harry" },
      { id: 14, type: "cat",   name: "Ingrid" },
      { id: 15, type: "other", name: "Xbar" },
      { id: 16, type: "cat",   name: "Jenn" },
      { id: 17, type: "cat",   name: "Kelly" },
      { id: 18, type: "other", name: "Xbar" },
      { id: 19, type: "other", name: "Xbar" },
      { id: 20, type: "cat",   name: "Larry" },
      { id: 21, type: "other", name: "Xbar" },
      { id: 22, type: "cat",   name: "Manny" },
      { id: 23, type: "dog",   name: "Nathan" },
      { id: 24, type: "cat",   name: "Ophelia" },
      { id: 25, type: "dog",   name: "Patrick" },
      { id: 26, type: "other", name: "Xbar" },
      { id: 27, type: "other", name: "Xbar" },
      { id: 28, type: "other", name: "Xbar" },
      { id: 29, type: "other", name: "Xbar" },
      { id: 30, type: "other", name: "Xbar" },
      { id: 31, type: "cat",   name: "Quincy" },
      { id: 32, type: "dog",   name: "Roger" }
    ];

    var view;
    run(this, function(){
      view = this.subject({
        content: A(content),
        height: 300,
        width: 500,
        rowHeight: 100,
        itemViews: {
          other: ListItemView.extend({
            rowHeight: 150,
            template: compile("Potato says {{name}} expected: other === {{type}} {{id}}")
          })
        },
        itemViewForIndex: function(idx){
          return this.itemViews[get(A(this.get('content')).objectAt(idx), 'type')] || ReusableListItemView;
        },

        heightForIndex: function(idx) {
          var view = this.itemViewForIndex(idx);

          return view.proto().rowHeight || this.get('rowHeight');
        }
      });
    });

    this.render();

    assert.equal(findItems(this).length, 4);
    assert.equal(view.get('totalHeight'), 3750);

    // expected
    // -----
    // 0   |
    // 1   |
    // 2   |
    // -----
    // 3   | <--- buffer
    // -----
    // 4   |
    // 5   |
    // 6   |
    // 7   |
    // 8   |
    // 9   |
    // 10  |
    // 11  |
    // 12  |
    // 13  |
    // 14  |
    // -----
    //
    assert.deepEqual(itemPositions(view), [
      { x:0, y:   0 }, // <- visible
      { x:0, y: 100 }, // <- visible
      { x:0, y: 200 }, // <- visible
      { x:0, y: 350 }  // <- buffer
    ] , "inDOM views are correctly positioned: before rowHeight change");

    run(view, 'set', 'rowHeight', 200);

    assert.equal(findItems(this).length, 3);
    assert.equal(view.get('totalHeight'), 5550);

    // expected
    // -----
    // 0   |
    // 1   |
    // ----|
    // 2   | <--- buffer
    // ----|
    // 3   |
    // 4   |
    // 5   |
    // 6   |
    // 7   |
    // 8   |
    // 9   |
    // 10  |
    // 11  |
    // 12  |
    // 13  |
    // 14  |
    // -----
    assert.deepEqual(itemPositions(view), [
      { x:0, y:    0 }, // <-- visible
      { x:0, y:  200 }, // <-- visible
      { x:0, y:  400 }  // <-- buffer
    ], "inDOM views are correctly positioned: after rowHeight change");
  });
});


================================================
FILE: tests/unit/percentage-layout-test.js
================================================
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, setupOnerror, resetOnerror } from '@ember/test-helpers';
import {
  generateContent, sortItemsByPosition, itemPositions } from '../helpers/helpers';
import template from '../templates/percentage';
import { gte } from 'ember-compatibility-helpers';

var itemWidth = 100;
var itemHeight = 50;
var width = 1000;
var height = 400;

// Since we are testing percentage based layouts we only want to test the top / left.
// The widths are calculated by percentages so then can be difficult to reproduce the browsers rounding
function extractTopLeftRounded(items) {
    return items.map(function(item) {
        return {top: Math.round(item.top), left: Math.round(item.left)};
    });
}

module('percentage layout', function(hooks) {
  setupRenderingTest(hooks);
  hooks.beforeEach(function() {
    // eslint-disable-next-line no-console
    console.debug('Note: Logged console errors are expected in the Asserts when... tests');
  })

  test("cells have correct width", async function(assert) {
    let columns = [25, 50, 15, 10];
    let content = generateContent(8);

    this.setProperties({height, width, itemHeight, itemWidth, content, columns});
    await render(template);

    let items = sortItemsByPosition(this.element);
    let positions = extractTopLeftRounded(itemPositions(this.element));

    // test the positioning done by the layout.
    assert.deepEqual(positions, [
        {top: 0, left: 0},
        {top: 0, left: 250},
        {top: 0, left: 750},
        {top: 0, left: 900},
        {top: 50, left: 0},
        {top: 50, left: 250},
        {top: 50, left: 750},
        {top: 50, left: 900},
    ]);

    // test that the widths match what was provided in `columns`
    assert.equal(items[0].style.width, '25%');
    assert.equal(items[1].style.width, '50%');
    assert.equal(items[2].style.width, '15%');
    assert.equal(items[3].style.width, '10%');
    assert.equal(items[4].style.width, '25%');
    assert.equal(items[5].style.width, '50%');
    assert.equal(items[6].style.width, '15%');
    assert.equal(items[7].style.width, '10%');

    assert.equal(items[0].getBoundingClientRect().height, itemHeight);
  });

  test("columns can use decimals", async function(assert) {
    let columns = [33.333, 66.666];
    let content = generateContent(6);

    this.setProperties({height, width, itemHeight, itemWidth, content, columns});
    await render(template);

    let items = sortItemsByPosition(this.element);
    let positions = extractTopLeftRounded(itemPositions(this.element));

    // test the positioning done by the layout
    assert.deepEqual(positions, [
        {top: 0, left: 0},
        {top: 0, left: 333},
        {top: 50, left: 0},
        {top: 50, left: 333},
        {top: 100, left: 0},
        {top: 100, left: 333}
    ]);

    // test that the widths match what was provided in `columns`
    assert.equal(items[0].style.width, '33.333%');
    assert.equal(items[1].style.width, '66.666%');
    assert.equal(items[2].style.width, '33.333%');
    assert.equal(items[3].style.width, '66.666%');
    assert.equal(items[0].getBoundingClientRect().height, itemHeight);

  });

  if (gte('2.18.0')) {
    test("Asserts when columns are larger than 100", async function(assert) {
      assert.expect(1);
      let columns = [100, 10];
      let content = generateContent(10);
      try {
        setupOnerror(() => { assert.ok(true); });
        this.setProperties({height, width, itemHeight, itemWidth, content, columns});
        await render(template);
      } finally {
        resetOnerror();
      }
    });

    test("Asserts when columns do not equal 100", async function(assert) {
      assert.expect(1);
      let columns = [10, 10];
      let content = generateContent(10);
      try {
        setupOnerror(() => { assert.ok(true); });
        this.setProperties({height, width, itemHeight, itemWidth, content, columns});
        await render(template);
      } finally {
        resetOnerror();
      }
    });
  } else {
    // TODO: write versions of these tests that work in 2.12 and 2.16
  }
});


================================================
FILE: tests/unit/raf-test.js
================================================
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import {
  generateContent,
  sortItemsByPosition
} from '../helpers/helpers';
import { hbs } from 'ember-cli-htmlbars';

let originalRaf = window.requestAnimationFrame;

let template = hbs`{{#if this.showComponent}}
<div style={{size-to-style this.width this.height}}>
<EmberCollection @items={{this.content}} @cell-layout={{fixed-grid-layout this.itemWidth this.itemHeight}} @estimated-width={{this.width}} @estimated-height={{this.height}} @scroll-left={{this.offsetX}} @scroll-top={{this.offsetY}} @buffer={{this.buffer}} @class="ember-collection" as |item|>
  <div class="list-item">{{item.name}}</div>
</EmberCollection>
</div>
{{/if}}`;

module('raf', function(hooks) {
  setupRenderingTest(hooks);

  hooks.beforeEach(function() {
    this.setup = function() {
      window.requestAnimationFrame = undefined;
    };

    this.teardown = function() {
      window.requestAnimationFrame = originalRaf;
    };
  });

  test('works without requestAnimationFrame', async function(assert) {

    var width = 150, height = 500, itemWidth = 50, itemHeight = 50;
    var offsetY = 100;
    var content = generateContent(5);

    this.setProperties({ width, height, itemWidth, itemHeight, content, offsetY, showComponent: true });
    await render(template);
    var positionSorted = sortItemsByPosition(this.element);

    assert.dom(positionSorted[0])
      .hasTextContaining("Item 1", "We rendered without requestAnimationFrame");

    // Force the component to be torn down.
    this.setProperties({showComponent: false});
  });
});


================================================
FILE: tests/unit/recycling-tests.js
================================================
import { run } from '@ember/runloop';
import Ember from 'ember';
import { setupRenderingTest } from 'ember-qunit';
import '@ember/test-helpers';
import { module, skip } from 'qunit';
import {
  generateContent,
  findItems,
  findVisibleItems
} from '../helpers/helpers';
// import { hbs } from 'ember-cli-htmlbars';

// TODO: Remove these declarations. They're just there to keep JSHint happy.
let compile, ListItemView, ReusableListItemView;

module('View recycling', function(hooks) {
  setupRenderingTest(hooks);

  skip("recycling complex views long list", function(assert){
    var content = generateContent(100),
      height = 50,
      rowHeight = 50,
      itemViewClass = ListItemView.extend({
        innerViewClass: Ember.View.extend({
          didInsertElement: function(){
            innerViewInsertionCount++;
          },
          willDestroyElement: function(){
            innerViewDestroyCount++;
          }
        }),
        template: compile("{{name}} {{#view view.innerViewClass}}{{/view}}")
      });

    var listViewInsertionCount, listViewDestroyCount,
      innerViewInsertionCount, innerViewDestroyCount;

    listViewInsertionCount = 0;
    listViewDestroyCount = 0;

    innerViewInsertionCount = 0;
    innerViewDestroyCount = 0;

    var view;
    run(this, function(){
      view = this.subject({
        content: content,
        height: height,
        rowHeight: rowHeight,
        itemViewClass: itemViewClass,
        scrollTop: 0,
        didInsertElement: function() {
          listViewInsertionCount++;
        },
        willDestroyElement: function() {
          listViewDestroyCount++;
        }
      });
    });

    assert.equal(listViewInsertionCount, 0, "expected number of listView's didInsertElement");
    assert.equal(listViewDestroyCount, 0, "expected number of listView's willDestroyElement");

    this.render();

    assert.equal(listViewInsertionCount, 1, "expected number of listView's didInsertElement");
    assert.equal(listViewDestroyCount, 0, "expected number of listView's willDestroyElement");

    assert.equal(innerViewInsertionCount, 2, "expected number of innerView's didInsertElement");
    assert.equal(innerViewDestroyCount, 0, "expected number of innerView's didInsertElement");

    assert.equal(findItems(this).length, 2, "The correct number of rows were rendered");

    innerViewInsertionCount = 0;
    innerViewDestroyCount = 0;

    run(function() {
      view.scrollTo(50);
    });

    assert.equal(findItems(this).length, 2, "The correct number of rows were rendered");

    assert.equal(innerViewInsertionCount, 1, "expected number of innerView's didInsertElement");
    assert.equal(innerViewDestroyCount, 1, "expected number of innerView's willDestroyElement");

    assert.equal(listViewInsertionCount, 1, "expected number of listView's didInsertElement");
    assert.equal(listViewDestroyCount, 0, "expected number of listView's willDestroyElement");

    innerViewInsertionCount = 0;
    innerViewDestroyCount = 0;

    run(function() {
      view.scrollTo(0);
    });

    assert.equal(findItems(this).length, 2, "The correct number of rows were rendered");

    assert.equal(innerViewInsertionCount, 1, "expected number of innerView's didInsertElement");
    assert.equal(innerViewDestroyCount, 1, "expected number of innerView's willDestroyElement");

    assert.equal(listViewInsertionCount, 1, "expected number of listView's didInsertElement");
    assert.equal(listViewDestroyCount, 0, "expected number of listView's willDestroyElement");

  });

  skip("recycling complex views short list", function(assert){
    var content = generateContent(2),
      height = 50,
      rowHeight = 50,
      itemViewClass = ListItemView.extend({
        innerViewClass: Ember.View.extend({
          didInsertElement: function(){
            innerViewInsertionCount++;
          },
          willDestroyElement: function(){
            innerViewDestroyCount++;
          }
        }),
        template: compile("{{name}} {{#view view.innerViewClass}}{{/view}}")
      });

    var listViewInsertionCount, listViewDestroyCount,
      innerViewInsertionCount, innerViewDestroyCount;

    listViewInsertionCount = 0;
    listViewDestroyCount = 0;

    innerViewInsertionCount = 0;
    innerViewDestroyCount = 0;

    var view;
    run(this, function(){
      view = this.subject({
        content: content,
        height: height,
        rowHeight: rowHeight,
        itemViewClass: itemViewClass,
        scrollTop: 0,
        didInsertElement: function() {
          listViewInsertionCount++;
        },
        willDestroyElement: function() {
          listViewDestroyCount++;
        }
      });
    });

    assert.equal(listViewInsertionCount, 0, "expected number of listView's didInsertElement (pre-append)");
    assert.equal(listViewDestroyCount, 0, "expected number of listView's willDestroyElement (pre-append)");

    this.render();

    assert.equal(listViewInsertionCount, 1, "expected number of listView's didInsertElement (post-append)");
    assert.equal(listViewDestroyCount, 0, "expected number of listView's willDestroyElement (post-append)");

    assert.equal(innerViewInsertionCount, 2, "expected number of innerView's didInsertElement (post-append)");
    assert.equal(innerViewDestroyCount, 0, "expected number of innerView's didInsertElement (post-append)");

    assert.equal(findItems(this).length, 2, "The correct number of rows were rendered");

    innerViewInsertionCount = 0;
    innerViewDestroyCount = 0;

    view.scrollTo(50);

    assert.equal(findItems(this).length, 2, "The correct number of rows were rendered (post-scroll to 50)");

    assert.equal(innerViewInsertionCount, 0, "expected number of innerView's didInsertElement (post-scroll to 50)");
    assert.equal(innerViewDestroyCount, 0, "expected number of innerView's willDestroyElement (post-scroll to 50)");

    assert.equal(listViewInsertionCount, 1, "expected number of listView's didInsertElement (post-scroll to 50)");
    assert.equal(listViewDestroyCount, 0, "expected number of listView's willDestroyElement (post-scroll to 50)");

    innerViewInsertionCount = 0;
    innerViewDestroyCount = 0;

    view.scrollTo(0);

    assert.equal(findItems(this).length, 2, "The correct number of rows were rendered (post-scroll to 0)");

    assert.equal(innerViewInsertionCount, 0, "expected number of innerView's didInsertElement (post-scroll to 0)");
    assert.equal(innerViewDestroyCount, 0, "expected number of innerView's willDestroyElement (post-scroll to 0)");

    assert.equal(listViewInsertionCount, 1, "expected number of listView's didInsertElement (post-scroll to 0)");
    assert.equal(listViewDestroyCount, 0, "expected number of listView's willDestroyElement (post-scroll to 0)");

  });

  skip("recycling complex views long list, with ReusableListItemView", function(assert){
    var content = generateContent(50),
      height = 50,
      rowHeight = 50,
      itemViewClass = Ember.ReusableListItemView.extend({
        innerViewClass: Ember.View.extend({
          didInsertElement: function(){
            innerViewInsertionCount++;
          },
          willDestroyElement: function(){
            innerViewDestroyCount++;
          }
        }),
        didInsertElement: function(){
          this._super();
          listItemViewInsertionCount++;
        },
        willDestroyElement: function(){
          this._super();
          listItemViewDestroyCount++;
        },
        template: compile("{{name}} {{#view view.innerViewClass}}{{/view}}")
      });

    var listViewInsertionCount, listViewDestroyCount,
      listItemViewInsertionCount, listItemViewDestroyCount,
      innerViewInsertionCount, innerViewDestroyCount;

    listViewInsertionCount = 0;
    listViewDestroyCount = 0;

    listItemViewInsertionCount = 0;
    listItemViewDestroyCount = 0;

    innerViewInsertionCount = 0;
    innerViewDestroyCount = 0;

    var view;
    run(this, function(){
      view = this.subject({
        content: content,
        height: height,
        rowHeight: rowHeight,
        itemViewClass: itemViewClass,
        scrollTop: 0,
        didInsertElement: function() {
          listViewInsertionCount++;
        },
        willDestroyElement: function() {
          listViewDestroyCount++;
        }
      });
    });

    assert.equal(listViewInsertionCount, 0, "expected number of listView's didInsertElement (pre-append)");
    assert.equal(listViewDestroyCount, 0, "expected number of listView's willDestroyElement (pre-append)");
    assert.equal(listItemViewInsertionCount, 0, "expected number of listItemView's didInsertElement (pre-append)");
    assert.equal(listItemViewDestroyCount, 0, "expected number of listItemView's willDestroyElement (pre-append)");
    assert.equal(innerViewInsertionCount, 0, "expected number of innerView's didInsertElement (pre-append)");
    assert.equal(innerViewDestroyCount, 0, "expected number of innerView's willDestroyElement (pre-append)");

    this.render();

    assert.equal(listViewInsertionCount, 1, "expected number of listView's didInsertElement (post-append)");
    assert.equal(listViewDestroyCount, 0, "expected number of listView's willDestroyElement (post-append)");

    assert.equal(listItemViewInsertionCount, 2, "expected number of listItemView's didInsertElement (post-append)");
    assert.equal(listItemViewDestroyCount, 0, "expected number of listItemView's didInsertElement (post-append)");

    assert.equal(innerViewInsertionCount, 2, "expected number of innerView's didInsertElement (post-append)");
    assert.equal(innerViewDestroyCount, 0, "expected number of innerView's didInsertElement (post-append)");

    assert.equal(findItems(this).length, 2, "The correct number of rows were rendered");

    listItemViewInsertionCount = 0;
    listItemViewDestroyCount = 0;
    innerViewInsertionCount = 0;
    innerViewDestroyCount = 0;

    view.scrollTo(50);

    assert.equal(findItems(this).length, 2, "The correct number of rows were rendered (post-scroll to 50)");

    assert.equal(listItemViewInsertionCount, 0, "expected number of listItemView's didInsertElement (post-scroll to 50)");
    assert.equal(listItemViewDestroyCount, 0, "expected number of listItemView's willDestroyElement (post-scroll to 50)");
    assert.equal(innerViewInsertionCount, 0, "expected number of innerView's didInsertElement (post-scroll to 50)");
    assert.equal(innerViewDestroyCount, 0, "expected number of innerView's willDestroyElement (post-scroll to 50)");

    listItemViewInsertionCount = 0;
    listItemViewDestroyCount = 0;
    innerViewInsertionCount = 0;
    innerViewDestroyCount = 0;

    view.scrollTo(0);

    assert.equal(findItems(this).length, 2, "The correct number of rows were rendered (post-scroll to 0)");

    assert.equal(listItemViewInsertionCount, 0, "expected number of listItemView's didInsertElement (post-scroll to 0)");
    assert.equal(listItemViewDestroyCount, 0, "expected number of listItemView's willDestroyElement (post-scroll to 0)");
    assert.equal(innerViewInsertionCount, 0, "expected number of innerView's didInsertElement (post-scroll to 0)");
    assert.equal(innerViewDestroyCount, 0, "expected number of innerView's willDestroyElement (post-scroll to 0)");
  });

  skip("recycling complex views short list, with ReusableListItemView", function(assert){
    var content = generateContent(2),
      height = 50,
      rowHeight = 50,
      itemViewClass = ReusableListItemView.extend({
        innerViewClass: Ember.View.extend({
          didInsertElement: function(){
            innerViewInsertionCount++;
          },
          willDestroyElement: function(){
            innerViewDestroyCount++;
          }
        }),
        didInsertElement: function(){
          this._super();
          listItemViewInsertionCount++;
        },
        willDestroyElement: function(){
          this._super();
          listItemViewDestroyCount++;
        },
        template: compile("{{name}} {{#view view.innerViewClass}}{{/view}}")
      });

    var listViewInsertionCount, listViewDestroyCount,
      listItemViewInsertionCount, listItemViewDestroyCount,
      innerViewInsertionCount, innerViewDestroyCount;

    listViewInsertionCount = 0;
    listViewDestroyCount = 0;

    listItemViewInsertionCount = 0;
    listItemViewDestroyCount = 0;

    innerViewInsertionCount = 0;
    innerViewDestroyCount = 0;

    var view;
    run(this, function(){
      view = this.subject({
        content: content,
        height: height,
        rowHeight: rowHeight,
        itemViewClass: itemViewClass,
        scrollTop: 0,
        didInsertElement: function() {
          listViewInsertionCount++;
        },
        willDestroyElement: function() {
          listViewDestroyCount++;
        }
      });
    });

    assert.equal(listViewInsertionCount, 0, "expected number of listView's didInsertElement (pre-append)");
    assert.equal(listViewDestroyCount, 0, "expected number of listView's willDestroyElement (pre-append)");
    assert.equal(listItemViewInsertionCount, 0, "expected number of listItemView's didInsertElement (pre-append)");
    assert.equal(listItemViewDestroyCount, 0, "expected number of listItemView's willDestroyElement (pre-append)");
    assert.equal(innerViewInsertionCount, 0, "expected number of innerView's didInsertElement (pre-append)");
    assert.equal(innerViewDestroyCount, 0, "expected number of innerView's willDestroyElement (pre-append)");

    this.render();

    assert.equal(listViewInsertionCount, 1, "expected number of listView's didInsertElement (post-append)");
    assert.equal(listViewDestroyCount, 0, "expected number of listView's willDestroyElement (post-append)");

    assert.equal(listItemViewInsertionCount, 2, "expected number of listItemView's didInsertElement (post-append)");
    assert.equal(listItemViewDestroyCount, 0, "expected number of listItemView's didInsertElement (post-append)");

    assert.equal(innerViewInsertionCount, 2, "expected number of innerView's didInsertElement (post-append)");
    assert.equal(innerViewDestroyCount, 0, "expected number of innerView's didInsertElement (post-append)");

    assert.equal(findItems(this).length, 2, "The correct number of rows were rendered");

    listItemViewInsertionCount = 0;
    listItemViewDestroyCount = 0;
    innerViewInsertionCount = 0;
    innerViewDestroyCount = 0;

    view.scrollTo(50);

    assert.equal(findItems(this).length, 2, "The correct number of rows were rendered (post-scroll to 50)");

    assert.equal(listItemViewInsertionCount, 0, "expected number of listItemView's didInsertElement (post-scroll to 50)");
    assert.equal(listItemViewDestroyCount, 0, "expected number of listItemView's willDestroyElement (post-scroll to 50)");
    assert.equal(innerViewInsertionCount, 0, "expected number of innerView's didInsertElement (post-scroll to 50)");
    assert.equal(innerViewDestroyCount, 0, "expected number of innerView's willDestroyElement (post-scroll to 50)");

    listItemViewInsertionCount = 0;
    listItemViewDestroyCount = 0;
    innerViewInsertionCount = 0;
    innerViewDestroyCount = 0;

    view.scrollTo(0);

    assert.equal(findItems(this).length, 2, "The correct number of rows were rendered (post-scroll to 0)");

    assert.equal(listItemViewInsertionCount, 0, "expected number of listItemView's didInsertElement (post-scroll to 0)");
    assert.equal(listItemViewDestroyCount, 0, "expected number of listItemView's willDestroyElement (post-scroll to 0)");
    assert.equal(innerViewInsertionCount, 0, "expected number of innerView's didInsertElement (post-scroll to 0)");
    assert.equal(innerViewDestroyCount, 0, "expected number of innerView's willDestroyElement (post-scroll to 0)");
  });

  skip("recycling complex views with ReusableListItemView, handling empty slots at the end of the grid", function(assert){
    var content = generateContent(20),
      height = 150,
      rowHeight = 50,
      width = 100,
      elementWidth = 50,
      itemViewClass = ReusableListItemView.extend({
        innerViewClass: Ember.View.extend({
          didInsertElement: function(){
            innerViewInsertionCount++;
          },
          willDestroyElement: function(){
            innerViewDestroyCount++;
          }
        }),
        didInsertElement: function(){
          this._super();
          listItemViewInsertionCount++;
        },
        willDestroyElement: function(){
          this._super();
          listItemViewDestroyCount++;
        },
        template: compile("{{name}} {{#view view.innerViewClass}}{{/view}}")
      });

    var listViewInsertionCount, listViewDestroyCount,
      listItemViewInsertionCount, listItemViewDestroyCount,
      innerViewInsertionCount, innerViewDestroyCount;

    listViewInsertionCount = 0;
    listViewDestroyCount = 0;

    listItemViewInsertionCount = 0;
    listItemViewDestroyCount = 0;

    innerViewInsertionCount = 0;
    innerViewDestroyCount = 0;

    var view;
    run(this, function(){
      view = this.subject({
        content: content,
        height: height,
        rowHeight: rowHeight,
        width: width,
        elementWidth: elementWidth,
        itemViewClass: itemViewClass,
        scrollTop: 0,
        didInsertElement: function() {
          listViewInsertionCount++;
        },
        willDestroyElement: function() {
          listViewDestroyCount++;
        }
      });
    });

    assert.equal(listViewInsertionCount, 0, "expected number of listView's didInsertElement (pre-append)");
    assert.equal(listViewDestroyCount, 0, "expected number of listView's willDestroyElement (pre-append)");
    assert.equal(listItemViewInsertionCount, 0, "expected number of listItemView's didInsertElement (pre-append)");
    assert.equal(listItemViewDestroyCount, 0, "expected number of listItemView's willDestroyElement (pre-append)");
    assert.equal(innerViewInsertionCount, 0, "expected number of innerView's didInsertElement (pre-append)");
    assert.equal(innerViewDestroyCount, 0, "expected number of innerView's willDestroyElement (pre-append)");

    this.render();

    assert.equal(listViewInsertionCount, 1, "expected number of listView's didInsertElement (post-append)");
    assert.equal(listViewDestroyCount, 0, "expected number of listView's willDestroyElement (post-append)");

    assert.equal(listItemViewInsertionCount, 8, "expected number of listItemView's didInsertElement (post-append)");
    assert.equal(listItemViewDestroyCount, 0, "expected number of listItemView's didInsertElement (post-append)");

    assert.equal(innerViewInsertionCount, 8, "expected number of innerView's didInsertElement (post-append)");
    assert.equal(innerViewDestroyCount, 0, "expected number of innerView's didInsertElement (post-append)");

    assert.equal(findItems(this).length, 8, "The correct number of items were rendered (post-append)");
    assert.equal(findVisibleItems(this).length, 8, "The number of items that are not hidden with display:none (post-append)");

    listItemViewInsertionCount = 0;
    listItemViewDestroyCount = 0;
    innerViewInsertionCount = 0;
    innerViewDestroyCount = 0;

    view.scrollTo(350);

    assert.equal(findItems(this).length, 8, "The correct number of items were rendered (post-scroll to 350)");
    assert.equal(findVisibleItems(this).length, 8, "The number of items that are not hidden with display:none (post-scroll to 350)");

    assert.equal(listItemViewInsertionCount, 0, "expected number of listItemView's didInsertElement (post-scroll to 350)");
    assert.equal(listItemViewDestroyCount, 0, "expected number of listItemView's willDestroyElement (post-scroll to 350)");
    assert.equal(innerViewInsertionCount, 0, "expected number of innerView's didInsertElement (post-scroll to 350)");
    assert.equal(innerViewDestroyCount, 0, "expected number of innerView's willDestroyElement (post-scroll to 350)");

    listItemViewInsertionCount = 0;
    listItemViewDestroyCount = 0;
    innerViewInsertionCount = 0;
    innerViewDestroyCount = 0;

    run(function() {
      view.set('width', 150);
    });

    assert.equal(findItems(this).length, 12, "The correct number of items were rendered (post-expand to 3 columns)");

    assert.equal(listItemViewInsertionCount, 4, "expected number of listItemView's didInsertElement (post-expand to 3 columns)");
    assert.equal(listItemViewDestroyCount, 0, "expected number of listItemView's willDestroyElement (post-expand to 3 columns)");
    assert.equal(innerViewInsertionCount, 4, "expected number of innerView's didInsertElement (post-expand to 3 columns)");
    assert.equal(innerViewDestroyCount, 0, "expected number of innerView's willDestroyElement (post-expand to 3 columns)");

    assert.equal(findVisibleItems(this).length, 12, "The number of items that are not hidden with display:none (post-expand to 3 columns)");
  });
});


================================================
FILE: tests/unit/scroll-top-test.js
================================================
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, settled } from '@ember/test-helpers';
import {
  findScrollable,
  generateContent,
  sortItemsByPosition,
  checkContent
} from '../helpers/helpers';
import template from '../templates/fixed-grid';

var raf = window.requestAnimationFrame;
if (raf === undefined) {
    raf = function(callback) {
        setTimeout(callback, 16);
    };
}

var size;
// lifted from antiscroll MIT license
function scrollbarSize() {
  if (size === undefined) {
    let div = document.createElement('div');

    div.classList.add('antiscroll-inner');
    div.style = 'width:50px;height:50px;overflow-y:scroll;position:absolute;top:-200px;left:-200px;';
    div.innerHTML = '<div style="height:100px;width:100%"/>';

    document.body.appendChild(div);
    
    var w1 = div.offsetWidth;
    var w2 = div.querySelector('div').offsetWidth;
    div.remove();

    size = w1 - w2;
  }

  return size;
}

function resolveAfterRaf() {
  return new Promise(resolve => raf(resolve));
}

var content = generateContent(5);

module('scrollTop', function(hooks) {
  setupRenderingTest(hooks);

  test("base case", async function(assert) {
    var width = 100, height = 500, itemWidth = 50, itemHeight = 50;
    var offsetY = 0;

    this.setProperties({ width, height, itemWidth, itemHeight, content, offsetY });
    await render(template);

    assert.equal(findScrollable(this.element).scrollTop, 0);

    var positionSorted = sortItemsByPosition(this.element);

    assert.dom(positionSorted[0])
      .hasTextContaining("Item 1", "The first item has not been hidden");

    this.set('width', 150);
    await settled();

    assert.equal(findScrollable(this.element).scrollTop, 0);
    checkContent(this, assert, 0, 5);
  });

  test("scroll but within content length", async function(assert){
    var width = 100+scrollbarSize(), height = 100, itemWidth = 50, itemHeight = 50;
    var offsetY = 100;

    this.setProperties({
      width, height, itemWidth, itemHeight, content, offsetY });
    await render(template);

    await resolveAfterRaf();

    assert.equal(Math.round(findScrollable(this.element).scrollTop), 50, 'Scrolled one row.');

    this.set('width', 150+scrollbarSize());

    await resolveAfterRaf();
    assert.equal(findScrollable(this.element).scrollTop, 0, 'No scroll with wider list.');

    var positionSorted = sortItemsByPosition(this.element);

    assert.dom(positionSorted[0])
      .hasTextContaining("Item 1", "The first item is not visible but in buffer.");
    checkContent(this, assert, 0, 5);
  });

  test("scroll within content length, beyond buffer", async function(assert){
    var width = 100+scrollbarSize(), height = 100, itemWidth = 50, itemHeight = 50;
    var offsetY = 0;

    this.setProperties({
      width, height, itemWidth, itemHeight, offsetY,
      buffer: 0,
      content: generateContent(10) });
    await render(template);

    let positionSorted = sortItemsByPosition(this.element);
    assert.dom(positionSorted[0])
      .hasTextContaining("Item 1", "The first cell should be the first item.");

    findScrollable(this.element).scrollTop = 151;
    await resolveAfterRaf();

    assert.equal(Math.round(findScrollable(this.element).scrollTop), 150, 'scrolled to item 7');

    positionSorted = sortItemsByPosition(this.element, true);

    assert.dom(positionSorted[0])
      .hasTextContaining("Item 7", "The items before what is on screen is not visible.");

    this.set('width', 200+scrollbarSize());
    await resolveAfterRaf();

    assert.equal(Math.round(findScrollable(this.element).scrollTop), 50, 'Scrolled down one row.');
    positionSorted = sortItemsByPosition(this.element, true);
    assert.dom(positionSorted[0])
      .hasTextContaining("Item 5", "The fifth item is first rendered.");
    checkContent(this, assert, 4, 5);
  });

  test("scroll but beyond content length", async function(assert) {
    var width = 100+scrollbarSize(), height = 500, itemWidth = 50, itemHeight = 50;
    var offsetY = 1000;

    this.setProperties({ width, height, itemWidth, itemHeight, content, offsetY });
    await render(template);

    assert.equal(findScrollable(this.element).scrollTop, 0);

    this.set('width', 150+scrollbarSize());
    await settled();

    assert.equal(findScrollable(this.element).scrollTop, 0);
    checkContent(this, assert, 0, 5);
  });
});


================================================
FILE: tests/unit/starting-index-test.js
================================================
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { generateContent } from '../helpers/helpers';
import template from '../templates/fixed-grid';

module('startingIndex', function(hooks) {
  setupRenderingTest(hooks);

  test("base case", async function(assert) {
    var width = 100, height = 500, itemWidth = 50, itemHeight = 50;
    var content = generateContent(5);

    this.setProperties({ width, height, itemWidth, itemHeight, content });
    await render(template);

    assert.equal(this.get('startingIndex', 0));
  });

  test("scroll but within content length", async function(assert) {
    var width = 100, height = 500, itemWidth = 50, itemHeight = 50;
    var content = generateContent(5);
    var offsetY = 100;

    this.setProperties({ width, height, itemWidth, itemHeight, content, offsetY });
    await render(template);

    assert.equal(this.get('startingIndex', 0));
  });

  test("scroll but beyond content length", async function(assert) {
    var width = 100, height = 500, itemWidth = 50, itemHeight = 50;
    var content = generateContent(20);
    var offsetY = 100;

    this.setProperties({ width, height, itemWidth, itemHeight, content, offsetY });
    await render(template);

    assert.equal(this.get('startingIndex', 0));
  });

  test("larger list", async function(assert) {
    var width = 100, height = 500, itemWidth = 50, itemHeight = 50;
    var content = generateContent(50);
    var offsetY = 100;

    this.setProperties({ width, height, itemWidth, itemHeight, content, offsetY });
    await render(template);

    assert.equal(this.get('startingIndex', 28));
  });

  test("larger list (2)", async function(assert) {
    var width = 100, height = 200, itemWidth = 50, itemHeight = 100;
    var content = generateContent(50);
    var offsetY = 100;

    this.setProperties({ width, height, itemWidth, itemHeight, content, offsetY });
    await render(template);

    assert.equal(this.get('startingIndex', 1));
  });
});


================================================
FILE: tests/unit/total-height-test.js
================================================
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { generateContent, findContainer } from '../helpers/helpers';
import template from '../templates/fixed-grid';

module('totalHeight', function(hooks) {
  setupRenderingTest(hooks);

  test("single column", async function(assert) {
    var width = 50, height = 500, itemHeight = 50, itemWidth = 50;
    var content = generateContent(20);

    this.setProperties({ width, height, itemWidth, itemHeight, content });
    await render(template);

    assert.equal(findContainer(this.element).getBoundingClientRect().height, 1000);
  });

  test("even", async function(assert) {
    var width = 120, height = 500, itemHeight = 50, itemWidth = 50;
    var content = generateContent(20);

    this.setProperties({ width, height, itemWidth, itemHeight, content });
    await render(template);

    assert.equal(findContainer(this.element).getBoundingClientRect().height, 500);
  });

  test("odd", async function(assert) {
    var width = 120, height = 500, itemHeight = 50, itemWidth = 50;
    var content = generateContent(21);

    this.setProperties({ width, height, itemWidth, itemHeight, content });
    await render(template);

    assert.equal(findContainer(this.element).getBoundingClientRect().height, 550);
  });
});
Download .txt
gitextract_ds3n4771/

├── .editorconfig
├── .ember-cli
├── .eslintignore
├── .eslintrc.js
├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .npmignore
├── .nvmrc
├── .template-lintrc.js
├── .watchmanconfig
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── RELEASE.md
├── addon/
│   ├── components/
│   │   ├── ember-collection/
│   │   │   └── template.hbs
│   │   ├── ember-collection.js
│   │   └── ember-native-scrollable.js
│   ├── layouts/
│   │   ├── grid.js
│   │   ├── mixed-grid.js
│   │   └── percentage-columns.js
│   └── utils/
│       ├── identity.js
│       ├── needs-revalidate.js
│       ├── style-generators.js
│       ├── style-properties.js
│       └── translate.js
├── app/
│   ├── components/
│   │   ├── ember-collection.js
│   │   └── ember-native-scrollable.js
│   └── helpers/
│       ├── fixed-grid-layout.js
│       ├── mixed-grid-layout.js
│       └── percentage-columns-layout.js
├── config/
│   ├── ember-try.js
│   └── environment.js
├── ember-cli-build.js
├── index.js
├── package.json
├── testem.js
└── tests/
    ├── acceptance/
    │   └── list-view-test.js
    ├── dummy/
    │   ├── app/
    │   │   ├── app.js
    │   │   ├── components/
    │   │   │   └── .gitkeep
    │   │   ├── controllers/
    │   │   │   ├── .gitkeep
    │   │   │   ├── mixed.js
    │   │   │   ├── percentages.js
    │   │   │   ├── scroll-position.js
    │   │   │   └── simple.js
    │   │   ├── helpers/
    │   │   │   ├── .gitkeep
    │   │   │   └── size-to-style.js
    │   │   ├── index.html
    │   │   ├── models/
    │   │   │   └── .gitkeep
    │   │   ├── resolver.js
    │   │   ├── router.js
    │   │   ├── routes/
    │   │   │   ├── .gitkeep
    │   │   │   ├── mixed.js
    │   │   │   ├── percentages.js
    │   │   │   ├── scroll-position.js
    │   │   │   └── simple.js
    │   │   ├── styles/
    │   │   │   └── app.css
    │   │   ├── templates/
    │   │   │   ├── application.hbs
    │   │   │   ├── components/
    │   │   │   │   └── .gitkeep
    │   │   │   ├── index.hbs
    │   │   │   ├── mixed.hbs
    │   │   │   ├── percentages.hbs
    │   │   │   ├── scroll-position.hbs
    │   │   │   └── simple.hbs
    │   │   └── utils/
    │   │       ├── fixtures.js
    │   │       ├── images.js
    │   │       └── make-model.js
    │   ├── config/
    │   │   ├── ember-cli-update.json
    │   │   ├── environment.js
    │   │   ├── optional-features.json
    │   │   └── targets.js
    │   └── public/
    │       ├── crossdomain.xml
    │       └── robots.txt
    ├── helpers/
    │   ├── destroy-app.js
    │   ├── helpers.js
    │   ├── module-for-acceptance.js
    │   ├── module-for-view.js
    │   └── start-app.js
    ├── index.html
    ├── templates/
    │   ├── fixed-grid.js
    │   ├── indexed.js
    │   └── percentage.js
    ├── test-helper.js
    └── unit/
        ├── .gitkeep
        ├── content-test.js
        ├── fixed-grid-test.js
        ├── layout-test.js
        ├── multi-height-list-view-test.js
        ├── percentage-layout-test.js
        ├── raf-test.js
        ├── recycling-tests.js
        ├── scroll-top-test.js
        ├── starting-index-test.js
        └── total-height-test.js
Download .txt
SYMBOL INDEX (108 symbols across 23 files)

FILE: addon/components/ember-collection.js
  class Cell (line 8) | class Cell {
    method constructor (line 9) | constructor(key, item, index, style) {
  function noop (line 18) | function noop() {}
  method init (line 23) | init() {
  method _needsRevalidate (line 63) | _needsRevalidate(){
  method didReceiveAttrs (line 72) | didReceiveAttrs() {
  method willDestroyElement (line 81) | willDestroyElement() {
  method updateItems (line 90) | updateItems(){
  method updateScrollPosition (line 114) | updateScrollPosition(){
  method updateContentSize (line 134) | updateContentSize() {
  method updateCells (line 149) | updateCells() {
  method _isGlimmer2 (line 221) | _isGlimmer2() {
  method scrollChange (line 225) | @action
  method clientSizeChange (line 239) | @action

FILE: addon/components/ember-native-scrollable.js
  method init (line 9) | init() {
  method didReceiveAttrs (line 17) | didReceiveAttrs() {
  method didInsertElement (line 22) | didInsertElement() {
  method didUpdate (line 29) | didUpdate() {
  method willDestroyElement (line 33) | willDestroyElement() {
  method applyStyle (line 37) | applyStyle() {
  method applyContentSize (line 54) | applyContentSize() {
  method syncScrollFromAttr (line 59) | syncScrollFromAttr() {
  method startScrollCheck (line 73) | startScrollCheck() {
  method cancelScrollCheck (line 88) | cancelScrollCheck() {
  method scrollCheck (line 98) | scrollCheck() {

FILE: addon/layouts/grid.js
  class Grid (line 4) | class Grid
    method constructor (line 6) | constructor(cellWidth, cellHeight) {
    method contentSize (line 11) | contentSize(clientWidth/*, clientHeight*/) {
    method indexAt (line 18) | indexAt(offsetX, offsetY, width, height) {
    method positionAt (line 22) | positionAt(index, width /*,height*/) {
    method widthAt (line 26) | widthAt(index) {
    method heightAt (line 30) | heightAt(index) {
    method count (line 34) | count(offsetX, offsetY, width, height) {
    method formatItemStyle (line 38) | formatItemStyle(itemIndex, clientWidth, clientHeight) {

FILE: addon/layouts/mixed-grid.js
  class MixedGrid (line 4) | class MixedGrid
    method constructor (line 6) | constructor(content, width) {
    method contentSize (line 11) | contentSize(clientWidth/*, clientHeight*/) {
    method indexAt (line 18) | indexAt(offsetX, offsetY, width, height) {
    method positionAt (line 22) | positionAt(index, width, height) {
    method widthAt (line 26) | widthAt(index) {
    method heightAt (line 30) | heightAt(index) {
    method count (line 34) | count(offsetX, offsetY, width, height) {
    method formatItemStyle (line 38) | formatItemStyle(itemIndex, clientWidth, clientHeight) {

FILE: addon/layouts/percentage-columns.js
  class PercentageColumns (line 5) | class PercentageColumns
    method constructor (line 10) | constructor(itemCount, columns, height) {
    method contentSize (line 36) | contentSize(clientWidth/*, clientHeight*/) {
    method indexAt (line 44) | indexAt(offsetX, offsetY, width, height) {
    method positionAt (line 48) | positionAt(index, width, height) {
    method widthAt (line 52) | widthAt(index) {
    method heightAt (line 56) | heightAt(index) {
    method count (line 60) | count(offsetX, offsetY, width, height) {
    method formatItemStyle (line 64) | formatItemStyle(itemIndex, clientWidth, clientHeight) {

FILE: addon/utils/identity.js
  function identity (line 3) | function identity(item) {

FILE: addon/utils/needs-revalidate.js
  function needsRevalidate (line 1) | function needsRevalidate(view){

FILE: addon/utils/style-generators.js
  function formatPixelStyle (line 3) | function formatPixelStyle(pos, width, height) {
  function formatPercentageStyle (line 10) | function formatPercentageStyle(pos, width, height) {

FILE: addon/utils/style-properties.js
  function findProperty (line 7) | function findProperty(property, css) {
  function styleProperty (line 21) | function styleProperty(prop) {
  function cssProperty (line 25) | function cssProperty(cssProp) {

FILE: addon/utils/translate.js
  function translatePosition (line 8) | function translatePosition(el, x, y) {
  function translateTransform2D (line 13) | function translateTransform2D(el, x, y) {
  function translateTransform3D (line 17) | function translateTransform3D(el, x, y) {
  function translatePositionCSS (line 21) | function translatePositionCSS(x, y) {
  function translateTransform2DCSS (line 25) | function translateTransform2DCSS(x, y) {
  function translateTransform3DCSS (line 29) | function translateTransform3DCSS(x, y) {
  function matrix2D (line 33) | function matrix2D(x, y) {
  function matrix3D (line 37) | function matrix3D(x, y) {

FILE: tests/dummy/app/controllers/percentages.js
  class PercentagesController (line 5) | class PercentagesController extends Controller {
    method changeColumn (line 8) | @action

FILE: tests/dummy/app/controllers/scroll-position.js
  class ScrollPositionController (line 5) | class ScrollPositionController extends Controller {
    method updateContainerWidth (line 13) | @action
    method updateContainerHeight (line 18) | @action
    method makeSquare (line 23) | @action
    method makeRow (line 29) | @action
    method makeLongRect (line 35) | @action
    method makeTallRect (line 41) | @action
    method scrollChange (line 47) | @action

FILE: tests/dummy/app/controllers/simple.js
  function shuffle (line 5) | function shuffle(array) {
  class SimpleController (line 24) | class SimpleController extends Controller {
    method updateContainerWidth (line 30) | @action
    method updateContainerHeight (line 35) | @action
    method shuffle (line 40) | @action
    method makeSquare (line 45) | @action
    method makeRow (line 51) | @action
    method makeLongRect (line 57) | @action
    method makeTallRect (line 63) | @action

FILE: tests/dummy/app/routes/mixed.js
  function getRandomInt (line 3) | function getRandomInt() {

FILE: tests/dummy/app/utils/make-model.js
  function makeModel (line 3) | function makeModel(count = 1000, imageArrayName = 'images') {

FILE: tests/helpers/destroy-app.js
  function destroyApp (line 3) | function destroyApp(application) {

FILE: tests/helpers/helpers.js
  function generateContent (line 6) | function generateContent(n) {
  function findScrollable (line 14) | function findScrollable(context) {
  function findContainer (line 18) | function findContainer(context) {
  function findItems (line 22) | function findItems(context) {
  function findVisibleItems (line 28) | function findVisibleItems(context) {
  function extractPosition (line 38) | function extractPosition(element) {
  function sortItemsByPosition (line 52) | function sortItemsByPosition(view, visibleOnly) {
  function sortElementsByPosition (line 58) | function sortElementsByPosition (elements) {
  function sortByPosition (line 66) | function sortByPosition(a, b) {
  function itemPositions (line 73) | function itemPositions(view) {
  function checkContent (line 79) | function checkContent(view, assert, expectedFirstItem, expectedCount) {

FILE: tests/helpers/module-for-acceptance.js
  method beforeEach (line 8) | beforeEach() {
  method afterEach (line 16) | afterEach() {

FILE: tests/helpers/module-for-view.js
  function moduleForView (line 74) | function moduleForView(name, description, callbacks) {

FILE: tests/helpers/start-app.js
  function startApp (line 6) | function startApp(attrs) {

FILE: tests/unit/content-test.js
  function joinContent (line 146) | function joinContent(context) {

FILE: tests/unit/percentage-layout-test.js
  function extractTopLeftRounded (line 16) | function extractTopLeftRounded(items) {

FILE: tests/unit/scroll-top-test.js
  function scrollbarSize (line 21) | function scrollbarSize() {
  function resolveAfterRaf (line 41) | function resolveAfterRaf() {
Condensed preview — 95 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (200K chars).
[
  {
    "path": ".editorconfig",
    "chars": 368,
    "preview": "# EditorConfig helps developers define and maintain consistent\n# coding styles between different editors and IDEs\n# edit"
  },
  {
    "path": ".ember-cli",
    "chars": 280,
    "preview": "{\n  /**\n    Ember CLI sends analytics information by default. The data is completely\n    anonymous, but there are times "
  },
  {
    "path": ".eslintignore",
    "chars": 240,
    "preview": "# unconventional js\n/blueprints/*/files/\n/vendor/\n\n# compiled output\n/dist/\n/tmp/\n\n# dependencies\n/bower_components/\n/no"
  },
  {
    "path": ".eslintrc.js",
    "chars": 1089,
    "preview": "module.exports = {\n  root: true,\n  parser: 'babel-eslint',\n  parserOptions: {\n    ecmaVersion: 2018,\n    sourceType: 'mo"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 1415,
    "preview": "name: CI\n\non:\n  push:\n    branches: [master]\n  pull_request:\n    branches: [master]\n\nconcurrency:\n  group: ci-${{ github"
  },
  {
    "path": ".gitignore",
    "chars": 393,
    "preview": "# See https://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\n/dist/\n/tmp/\n\n# dependenci"
  },
  {
    "path": ".npmignore",
    "chars": 401,
    "preview": "# compiled output\n/dist/\n/tmp/\n\n# dependencies\n/bower_components/\n\n# misc\n/.bowerrc\n/.editorconfig\n/.ember-cli\n/.env*\n/."
  },
  {
    "path": ".nvmrc",
    "chars": 3,
    "preview": "14\n"
  },
  {
    "path": ".template-lintrc.js",
    "chars": 246,
    "preview": "'use strict';\n\nmodule.exports = {\n  extends: 'recommended',\n  rules: {\n    'no-inline-styles': false,\n    'no-quoteless-"
  },
  {
    "path": ".watchmanconfig",
    "chars": 37,
    "preview": "{\n  \"ignore_dirs\": [\"tmp\", \"dist\"]\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 18825,
    "preview": "## v3.0.0 (2023-11-01)\n\n[BREAKING] Require Node 16+\n\n#### :boom: Breaking Change\n* [#222](https://github.com/adopted-emb"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 326,
    "preview": "The Ember team and community are committed to everyone having a safe and inclusive experience.\n\n**Our Community Guidelin"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 633,
    "preview": "# How To Contribute\n\n## Installation\n\n* `git clone <repository-url>`\n* `cd my-addon`\n* `npm install`\n\n## Linting\n\n* `npm"
  },
  {
    "path": "LICENSE.md",
    "chars": 1023,
    "preview": "Permission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentati"
  },
  {
    "path": "README.md",
    "chars": 7523,
    "preview": "# Ember Collection\n\n[![Github Actions](https://github.com/adopted-ember-addons/ember-collection/workflows/CI/badge.svg)]"
  },
  {
    "path": "RELEASE.md",
    "chars": 2264,
    "preview": "# Release\n\nReleases are mostly automated using\n[release-it](https://github.com/release-it/release-it/) and\n[lerna-change"
  },
  {
    "path": "addon/components/ember-collection/template.hbs",
    "chars": 374,
    "preview": "<EmberNativeScrollable @content-size={{this._contentSize}} @scroll-left={{this._scrollLeft}} @scroll-top={{this._scrollT"
  },
  {
    "path": "addon/components/ember-collection.js",
    "chars": 7684,
    "preview": "import { A } from '@ember/array';\nimport Component from '@ember/component';\nimport { action, set, get } from '@ember/obj"
  },
  {
    "path": "addon/components/ember-native-scrollable.js",
    "chars": 3883,
    "preview": "import { join } from '@ember/runloop';\nimport Component from '@ember/component';\nimport { translate } from 'ember-collec"
  },
  {
    "path": "addon/layouts/grid.js",
    "chars": 1187,
    "preview": "import FixedGrid from 'layout-bin-packer/fixed-grid';\nimport { formatPixelStyle } from '../utils/style-generators';\n\nexp"
  },
  {
    "path": "addon/layouts/mixed-grid.js",
    "chars": 1188,
    "preview": "import ShelfFirst from 'layout-bin-packer/shelf-first';\nimport { formatPixelStyle } from '../utils/style-generators';\n\ne"
  },
  {
    "path": "addon/layouts/percentage-columns.js",
    "chars": 2259,
    "preview": "import { assert } from '@ember/debug';\nimport ShelfFirst from 'layout-bin-packer/shelf-first';\nimport { formatPercentage"
  },
  {
    "path": "addon/utils/identity.js",
    "chars": 252,
    "preview": "import { guidFor } from '@ember/object/internals';\n\nexport default function identity(item) {\n  let key;\n  let type = typ"
  },
  {
    "path": "addon/utils/needs-revalidate.js",
    "chars": 213,
    "preview": "export default function needsRevalidate(view){\n  view._renderNode.isDirty = true;\n  view._renderNode.ownerNode.emberView"
  },
  {
    "path": "addon/utils/style-generators.js",
    "chars": 477,
    "preview": "import { translateCSS } from './translate';\n\nexport function formatPixelStyle(pos, width, height) {\n  let css = 'positio"
  },
  {
    "path": "addon/utils/style-properties.js",
    "chars": 817,
    "preview": "import { capitalize, camelize } from '@ember/string';\nconst stylePrefixes  = ['webkit', 'Webkit',  'ms',  'Moz',  'O'];\n"
  },
  {
    "path": "addon/utils/translate.js",
    "chars": 1333,
    "preview": "import { styleProperty, cssProperty } from './style-properties';\n\nconst transformCSSProp   = cssProperty('transform');\nc"
  },
  {
    "path": "app/components/ember-collection.js",
    "chars": 72,
    "preview": "export { default } from 'ember-collection/components/ember-collection';\n"
  },
  {
    "path": "app/components/ember-native-scrollable.js",
    "chars": 79,
    "preview": "export { default } from 'ember-collection/components/ember-native-scrollable';\n"
  },
  {
    "path": "app/helpers/fixed-grid-layout.js",
    "chars": 188,
    "preview": "import { helper } from '@ember/component/helper';\nimport Grid from 'ember-collection/layouts/grid';\n\nexport default help"
  },
  {
    "path": "app/helpers/mixed-grid-layout.js",
    "chars": 193,
    "preview": "import { helper } from '@ember/component/helper';\nimport MixedGrid from 'ember-collection/layouts/mixed-grid';\n\nexport d"
  },
  {
    "path": "app/helpers/percentage-columns-layout.js",
    "chars": 239,
    "preview": "import { helper } from '@ember/component/helper';\nimport PercentageColumns from 'ember-collection/layouts/percentage-col"
  },
  {
    "path": "config/ember-try.js",
    "chars": 1595,
    "preview": "'use strict';\n\nconst getChannelURL = require('ember-source-channel-url');\n\nmodule.exports = async function() {\n  return "
  },
  {
    "path": "config/environment.js",
    "chars": 90,
    "preview": "'use strict';\n\nmodule.exports = function(/* environment, appConfig */) {\n  return { };\n};\n"
  },
  {
    "path": "ember-cli-build.js",
    "chars": 494,
    "preview": "'use strict';\n\nconst EmberAddon = require('ember-cli/lib/broccoli/ember-addon');\n\nmodule.exports = function(defaults) {\n"
  },
  {
    "path": "index.js",
    "chars": 71,
    "preview": "'use strict';\n\nmodule.exports = {\n  name: require('./package').name\n};\n"
  },
  {
    "path": "package.json",
    "chars": 2516,
    "preview": "{\n  \"name\": \"ember-collection\",\n  \"version\": \"3.0.0\",\n  \"description\": \"An efficient incremental rendering component wit"
  },
  {
    "path": "testem.js",
    "chars": 717,
    "preview": "module.exports = {\n  test_page: 'tests/index.html?hidepassed',\n  disable_watching: true,\n  launch_in_ci: [\n    'Chrome',"
  },
  {
    "path": "tests/acceptance/list-view-test.js",
    "chars": 36647,
    "preview": "import { currentURL, visit } from '@ember/test-helpers';\nimport { run } from '@ember/runloop';\nimport { module, skip } f"
  },
  {
    "path": "tests/dummy/app/app.js",
    "chars": 375,
    "preview": "import Application from '@ember/application';\nimport Resolver from './resolver';\nimport loadInitializers from 'ember-loa"
  },
  {
    "path": "tests/dummy/app/components/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/dummy/app/controllers/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/dummy/app/controllers/mixed.js",
    "chars": 88,
    "preview": "import Controller from '@ember/controller';\n\nexport default class extends Controller {}\n"
  },
  {
    "path": "tests/dummy/app/controllers/percentages.js",
    "chars": 690,
    "preview": "import Controller from '@ember/controller';\nimport { action } from '@ember/object';\nimport { tracked } from '@glimmer/tr"
  },
  {
    "path": "tests/dummy/app/controllers/scroll-position.js",
    "chars": 1038,
    "preview": "import Controller from '@ember/controller';\nimport { action } from '@ember/object';\nimport { tracked } from '@glimmer/tr"
  },
  {
    "path": "tests/dummy/app/controllers/simple.js",
    "chars": 1432,
    "preview": "import Controller from '@ember/controller';\nimport { action } from '@ember/object';\nimport { tracked } from '@glimmer/tr"
  },
  {
    "path": "tests/dummy/app/helpers/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/dummy/app/helpers/size-to-style.js",
    "chars": 232,
    "preview": "import { htmlSafe } from '@ember/template';\nimport { helper } from '@ember/component/helper';\n\nexport default helper(fun"
  },
  {
    "path": "tests/dummy/app/index.html",
    "chars": 912,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n   "
  },
  {
    "path": "tests/dummy/app/models/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/dummy/app/resolver.js",
    "chars": 65,
    "preview": "import Resolver from 'ember-resolver';\n\nexport default Resolver;\n"
  },
  {
    "path": "tests/dummy/app/router.js",
    "chars": 354,
    "preview": "import EmberRouter from '@ember/routing/router';\nimport config from './config/environment';\n\nconst Router = EmberRouter."
  },
  {
    "path": "tests/dummy/app/routes/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/dummy/app/routes/mixed.js",
    "chars": 473,
    "preview": "import Route from '@ember/routing/route';\n\nfunction getRandomInt() {\n  return Math.floor(Math.random() * (251) + 75);\n}\n"
  },
  {
    "path": "tests/dummy/app/routes/percentages.js",
    "chars": 143,
    "preview": "import Route from '@ember/routing/route';\nimport makeModel from '../utils/make-model';\n\nexport default Route.extend({\n  "
  },
  {
    "path": "tests/dummy/app/routes/scroll-position.js",
    "chars": 143,
    "preview": "import Route from '@ember/routing/route';\nimport makeModel from '../utils/make-model';\n\nexport default Route.extend({\n  "
  },
  {
    "path": "tests/dummy/app/routes/simple.js",
    "chars": 143,
    "preview": "import Route from '@ember/routing/route';\nimport makeModel from '../utils/make-model';\n\nexport default Route.extend({\n  "
  },
  {
    "path": "tests/dummy/app/styles/app.css",
    "chars": 4155,
    "preview": "body {\n   padding-top: 70px;\n}\n\ncode {\n    padding: 0;\n    padding-top: 0.2em;\n    padding-bottom: 0.2em;\n    margin: 0;"
  },
  {
    "path": "tests/dummy/app/templates/application.hbs",
    "chars": 1071,
    "preview": "<nav class=\"navbar navbar-inverse navbar-fixed-top\">\n    <div class=\"container\">\n    <div class=\"navbar-header\">\n       "
  },
  {
    "path": "tests/dummy/app/templates/components/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/dummy/app/templates/index.hbs",
    "chars": 1970,
    "preview": "<div class=\"jumbotron\">\n    <h1>Ember Collection Demos</h1>\n    <p>These are the demos for the <a href=\"https://github.c"
  },
  {
    "path": "tests/dummy/app/templates/mixed.hbs",
    "chars": 310,
    "preview": "<div class=\"mixed\" style=\"position:relative;height:500px\">\n  <EmberCollection @items={{this.model}} @estimated-height={{"
  },
  {
    "path": "tests/dummy/app/templates/percentages.hbs",
    "chars": 675,
    "preview": "<button {{on 'click' (fn this.changeColumn 1)}}>25-50-25</button>\n<button {{on 'click' (fn this.changeColumn 2)}}>20-20-"
  },
  {
    "path": "tests/dummy/app/templates/scroll-position.hbs",
    "chars": 1482,
    "preview": "<h3>Scroll Position</h3>\n<button {{on 'click' this.makeSquare}}>Square</button>\n<button {{on 'click' this.makeRow}}>Row<"
  },
  {
    "path": "tests/dummy/app/templates/simple.hbs",
    "chars": 1101,
    "preview": "<h3>Simple</h3>\n<button {{on 'click' this.makeSquare}}>Square</button>\n<button {{on 'click' this.makeRow}}>Row</button>\n"
  },
  {
    "path": "tests/dummy/app/utils/fixtures.js",
    "chars": 1372,
    "preview": "export var types = [\n  {id:  1, type: \"cat\",   name: \"Andrew\"},\n  {id:  2, type: \"cat\",   name: \"Andrew\"},\n  {id:  3, ty"
  },
  {
    "path": "tests/dummy/app/utils/images.js",
    "chars": 1313,
    "preview": "var images = [\n  'images/ebryn.jpg',\n  'images/iterzic.jpg',\n  'images/kselden.jpg',\n  'images/machty.jpg',\n  'images/rw"
  },
  {
    "path": "tests/dummy/app/utils/make-model.js",
    "chars": 383,
    "preview": "import images from './images';\n\nexport default function makeModel(count = 1000, imageArrayName = 'images') {\n  var image"
  },
  {
    "path": "tests/dummy/config/ember-cli-update.json",
    "chars": 446,
    "preview": "{\n  \"schemaVersion\": 0,\n  \"packages\": [\n    {\n      \"name\": \"ember-cli\",\n      \"version\": \"3.12.0\",\n      \"blueprints\": "
  },
  {
    "path": "tests/dummy/config/environment.js",
    "chars": 1241,
    "preview": "'use strict';\n\nmodule.exports = function(environment) {\n  let ENV = {\n    modulePrefix: 'dummy',\n    environment,\n    ro"
  },
  {
    "path": "tests/dummy/config/optional-features.json",
    "chars": 154,
    "preview": "{\n  \"application-template-wrapper\": false,\n  \"default-async-observers\": true,\n  \"jquery-integration\": false,\n  \"template"
  },
  {
    "path": "tests/dummy/config/targets.js",
    "chars": 305,
    "preview": "'use strict';\n\nconst browsers = [\n  'last 1 Chrome versions',\n  'last 1 Firefox versions',\n  'last 1 Safari versions'\n];"
  },
  {
    "path": "tests/dummy/public/crossdomain.xml",
    "chars": 585,
    "preview": "<?xml version=\"1.0\"?>\n<!DOCTYPE cross-domain-policy SYSTEM \"http://www.adobe.com/xml/dtds/cross-domain-policy.dtd\">\n<cro"
  },
  {
    "path": "tests/dummy/public/robots.txt",
    "chars": 51,
    "preview": "# http://www.robotstxt.org\nUser-agent: *\nDisallow:\n"
  },
  {
    "path": "tests/helpers/destroy-app.js",
    "chars": 122,
    "preview": "import { run } from '@ember/runloop';\n\nexport default function destroyApp(application) {\n  run(application, 'destroy');\n"
  },
  {
    "path": "tests/helpers/helpers.js",
    "chars": 4053,
    "preview": "import { A } from '@ember/array';\nimport { get } from '@ember/object';\nimport Ember from 'ember';\nvar compile = Ember.Ha"
  },
  {
    "path": "tests/helpers/module-for-acceptance.js",
    "chars": 576,
    "preview": "import { module } from 'qunit';\nimport { resolve } from 'rsvp';\nimport startApp from '../helpers/start-app';\nimport dest"
  },
  {
    "path": "tests/helpers/module-for-view.js",
    "chars": 2639,
    "preview": "import { deprecate } from '@ember/application/deprecations';\nimport { tryInvoke } from '@ember/utils';\nimport { run } fr"
  },
  {
    "path": "tests/helpers/start-app.js",
    "chars": 539,
    "preview": "import Application from '../../app';\nimport config from '../../config/environment';\nimport { merge } from '@ember/polyfi"
  },
  {
    "path": "tests/index.html",
    "chars": 1320,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n   "
  },
  {
    "path": "tests/templates/fixed-grid.js",
    "chars": 559,
    "preview": "import { hbs } from 'ember-cli-htmlbars';\n\nexport default hbs`  {{!-- template-lint-disable no-curly-component-invocatio"
  },
  {
    "path": "tests/templates/indexed.js",
    "chars": 562,
    "preview": "import { hbs } from 'ember-cli-htmlbars';\n\nexport default hbs`  {{!-- template-lint-disable no-curly-component-invocatio"
  },
  {
    "path": "tests/templates/percentage.js",
    "chars": 585,
    "preview": "import { hbs } from 'ember-cli-htmlbars';\n\nexport default hbs`  {{!-- template-lint-disable no-curly-component-invocatio"
  },
  {
    "path": "tests/test-helper.js",
    "chars": 303,
    "preview": "/* global QUnit */\nimport Application from '../app';\nimport config from '../config/environment';\nimport { setApplication"
  },
  {
    "path": "tests/unit/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/unit/content-test.js",
    "chars": 5379,
    "preview": "import ArrayProxy from '@ember/array/proxy';\nimport { A } from '@ember/array';\nimport { module, test } from 'qunit';\nimp"
  },
  {
    "path": "tests/unit/fixed-grid-test.js",
    "chars": 813,
    "preview": "import { module, test } from 'qunit';\nimport { setupRenderingTest } from 'ember-qunit';\nimport { render } from '@ember/t"
  },
  {
    "path": "tests/unit/layout-test.js",
    "chars": 1517,
    "preview": "import { hbs } from 'ember-cli-htmlbars';\nimport { module, test } from 'qunit';\nimport { setupRenderingTest } from 'embe"
  },
  {
    "path": "tests/unit/multi-height-list-view-test.js",
    "chars": 12637,
    "preview": "import { get } from '@ember/object';\nimport { A } from '@ember/array';\nimport { run } from '@ember/runloop';\nimport { se"
  },
  {
    "path": "tests/unit/percentage-layout-test.js",
    "chars": 4160,
    "preview": "import { module, test } from 'qunit';\nimport { setupRenderingTest } from 'ember-qunit';\nimport { render, setupOnerror, r"
  },
  {
    "path": "tests/unit/raf-test.js",
    "chars": 1670,
    "preview": "import { module, test } from 'qunit';\nimport { setupRenderingTest } from 'ember-qunit';\nimport { render } from '@ember/t"
  },
  {
    "path": "tests/unit/recycling-tests.js",
    "chars": 20928,
    "preview": "import { run } from '@ember/runloop';\nimport Ember from 'ember';\nimport { setupRenderingTest } from 'ember-qunit';\nimpor"
  },
  {
    "path": "tests/unit/scroll-top-test.js",
    "chars": 4417,
    "preview": "import { module, test } from 'qunit';\nimport { setupRenderingTest } from 'ember-qunit';\nimport { render, settled } from "
  },
  {
    "path": "tests/unit/starting-index-test.js",
    "chars": 2054,
    "preview": "import { module, test } from 'qunit';\nimport { setupRenderingTest } from 'ember-qunit';\nimport { render } from '@ember/t"
  },
  {
    "path": "tests/unit/total-height-test.js",
    "chars": 1359,
    "preview": "import { module, test } from 'qunit';\nimport { setupRenderingTest } from 'ember-qunit';\nimport { render } from '@ember/t"
  }
]

About this extraction

This page contains the full source code of the emberjs/ember-collection GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 95 files (182.2 KB), approximately 52.6k tokens, and a symbol index with 108 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!