Index: frontend/node_modules/hoopy/.eslintrc
===================================================================
--- frontend/node_modules/hoopy/.eslintrc	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/hoopy/.eslintrc	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,205 @@
+env:
+  es6: true
+  node: true
+rules:
+  accessor-pairs: [ 2, { "setWithoutGet": true } ]
+  array-bracket-spacing: [ 2, "always" ]
+  array-callback-return: 2
+  arrow-body-style: 0
+  arrow-parens: 0
+  arrow-spacing: [ 2, { "before": true, "after": true } ]
+  block-scoped-var: 1
+  block-spacing: [ 2, "always" ]
+  brace-style: [ 2, "1tbs" ]
+  callback-return: 0
+  camelcase: [ 2, { "properties": "always" } ]
+  comma-dangle: 0
+  comma-spacing: [ 2, { "before": false, "after": true } ]
+  computed-property-spacing: [ 2, "never" ]
+  consistent-return: 0
+  consistent-this: 0
+  constructor-super: 0
+  curly: [ 2, "all" ]
+  default-case: 0
+  dot-location: [ 2, "property" ]
+  dot-notation: [ 2, { "allowKeywords": true } ]
+  eol-last: 2
+  eqeqeq: 2
+  func-names: 0
+  func-style: [ 2, "declaration" ]
+  generator-star-spacing: [ 2, "after" ]
+  global-require: 0
+  guard-for-in: 2
+  handle-callback-err: [ 2, "error" ]
+  id-blacklist: 0
+  id-length: 0
+  id-match: 0
+  indent: [ 2, 2, { "SwitchCase": 1 } ]
+  init-declarations: 0
+  jsx-quotes: 0
+  key-spacing: [ 2, { "beforeColon": false, "afterColon": true } ]
+  keyword-spacing: [ 2, { "before": true, "after": true } ]
+  linebreak-style: [ 2, "unix" ]
+  lines-around-comment: 0
+  max-depth: [ 1, 4 ]
+  max-nested-callbacks: [ 1, 2 ]
+  max-params: [ 1, 4 ]
+  max-statements: 0
+  new-cap: 2
+  new-parens: 2
+  newline-after-var: 0
+  newline-before-return: 0
+  newline-per-chained-call: 1
+  no-alert: 2
+  no-array-constructor: 2
+  no-bitwise: 1
+  no-caller: 2
+  no-case-declarations: 2
+  no-catch-shadow: 2
+  no-class-assign: 2
+  no-cond-assign: [ 2, "always" ]
+  no-confusing-arrow: 0
+  no-console: 1
+  no-constant-condition: 2
+  no-const-assign: 2
+  no-continue: 0
+  no-control-regex: 2
+  no-debugger: 2
+  no-delete-var: 2
+  no-div-regex: 0
+  no-dupe-args: 2
+  no-dupe-class-members: 2
+  no-dupe-keys: 2
+  no-duplicate-case: 2
+  no-else-return: 2
+  no-empty: 2
+  no-empty-character-class: 2
+  no-empty-function: 0
+  no-empty-pattern: 2
+  no-eq-null: 2
+  no-eval: 2
+  no-ex-assign: 2
+  no-extend-native: 2
+  no-extra-bind: 2
+  no-extra-boolean-cast: 2
+  no-extra-label: 2
+  no-extra-parens: [ 2, "all" ]
+  no-extra-semi: 2
+  no-fallthrough: 1
+  no-floating-decimal: 2
+  no-func-assign: 2
+  no-implicit-coercion: 0
+  no-implicit-globals: 0
+  no-implied-eval: 2
+  no-inline-comments: 2
+  no-inner-declarations: [ 2, "both" ]
+  no-invalid-regexp: 2
+  no-invalid-this: 0
+  no-irregular-whitespace: 2
+  no-iterator: 2
+  no-labels: 2
+  no-label-var: 2
+  no-lonely-if: 2
+  no-lone-blocks: 1
+  no-loop-func: 2
+  no-magic-numbers: 0
+  no-mixed-requires: 0
+  no-mixed-spaces-and-tabs: 2
+  no-multiple-empty-lines: [ 2, { "max": 1, "maxEOF": 1, "maxBOF": 0 } ]
+  no-multi-spaces: 2
+  no-multi-str: 2
+  no-native-reassign: 2
+  no-negated-condition: 2
+  no-negated-in-lhs: 2
+  no-nested-ternary: 2
+  no-new: 1
+  no-new-func: 2
+  no-new-object: 2
+  no-new-require: 2
+  no-new-symbol: 2
+  no-new-wrappers: 2
+  no-obj-calls: 2
+  no-octal: 2
+  no-octal-escape: 2
+  no-param-reassign: 0
+  no-path-concat: 2
+  no-plusplus: 0
+  no-process-env: 0
+  no-process-exit: 1
+  no-proto: 2
+  no-redeclare: [ 2, { "builtinGlobals": true } ]
+  no-regex-spaces: 2
+  no-restricted-globals: 2
+  no-restricted-imports: 0
+  no-restricted-modules: 0
+  no-restricted-syntax: 0
+  no-return-assign: 0
+  no-script-url: 2
+  no-self-assign: 2
+  no-self-compare: 2
+  no-sequences: 2
+  no-shadow: [ 2, { "hoist": "all" } ]
+  no-shadow-restricted-names: 2
+  no-spaced-func: 2
+  no-sparse-arrays: 2
+  no-sync: 0
+  no-ternary: 1
+  no-this-before-super: 2
+  no-throw-literal: 1
+  no-trailing-spaces: 2
+  no-undef: 2
+  no-undef-init: 2
+  no-undefined: 0
+  no-underscore-dangle: 2
+  no-unexpected-multiline: 2
+  no-unmodified-loop-condition: 2
+  no-unneeded-ternary: 2
+  no-unreachable: 2
+  no-unused-expressions: 2
+  no-unused-labels: 2
+  no-unused-vars: [ 2, "all" ]
+  no-useless-call: 2
+  no-useless-concat: 2
+  no-useless-constructor: 2
+  no-use-before-define: 0
+  no-var: 1
+  no-void: 2
+  no-warning-comments: 1
+  no-whitespace-before-property: 2
+  no-with: 2
+  object-curly-spacing: [ 2, "always" ]
+  object-shorthand: [ 2, "always" ]
+  one-var: 0
+  one-var-declaration-per-line: 0
+  operator-assignment: [ 2, "always" ]
+  operator-linebreak: [ 2, "after" ]
+  padded-blocks: [ 2, "never" ]
+  prefer-arrow-callback: 2
+  prefer-const: 2
+  prefer-reflect: 0
+  prefer-rest-params: 2
+  prefer-spread: 2
+  prefer-template: 2
+  quote-props: [ 2, "as-needed" ]
+  radix: 0
+  require-jsdoc: 0
+  require-yield: 2
+  semi: [ 2, "never" ]
+  semi-spacing: 0
+  sort-imports: 0
+  sort-vars: 0
+  space-before-blocks: [ 2, "always" ]
+  space-before-function-paren: [ 2, { "anonymous": "always", "named": "always" } ]
+  space-infix-ops: 2
+  space-unary-ops: 0
+  spaced-comment: [ 1, "always" ]
+  strict: [ 2, "global" ]
+  template-curly-spacing: [ 2, "never" ]
+  use-isnan: 2
+  valid-jsdoc: 0
+  valid-typeof: 2
+  vars-on-top: 0
+  wrap-iife: 2
+  wrap-regex: 0
+  yield-star-spacing: [ 2, { "before": false, "after": true } ]
+  yoda: [ 2, "never" ]
Index: frontend/node_modules/hoopy/.gitlab-ci.yml
===================================================================
--- frontend/node_modules/hoopy/.gitlab-ci.yml	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/hoopy/.gitlab-ci.yml	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,25 @@
+cache:
+  key: "$CI_JOB_NAME"
+  untracked: true
+  paths:
+    - node_modules/
+
+before_script:
+  - npm install
+
+.test_template: &npm_test
+  script:
+    - npm run lint
+    - npm test
+
+test:node6:
+  image: node:6
+  <<: *npm_test
+
+test:node8:
+  image: node:8
+  <<: *npm_test
+
+test:node10:
+  image: node:10
+  <<: *npm_test
Index: frontend/node_modules/hoopy/AUTHORS
===================================================================
--- frontend/node_modules/hoopy/AUTHORS	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/hoopy/AUTHORS	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+Phil Booth <pmbooth@gmail.com> (https://philbooth.me/)
+James M. Greene (https://github.com/JamesMGreene)
+
Index: frontend/node_modules/hoopy/CHANGELOG.md
===================================================================
--- frontend/node_modules/hoopy/CHANGELOG.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/hoopy/CHANGELOG.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,37 @@
+# Change log
+
+## 0.1.4
+
+### Other changes
+
+* project: update dev dependencies (e0b36a9)
+* project: migrate to gitlab (af3f598)
+
+## 0.1.3
+
+* fix: note minimum required node version (37e4481)
+* chore: update authors (cc53233)
+
+## 0.1.2
+
+* chore: add unit test of Array.prototype.slice (557cdb7)
+* fix: handle error when attempting to coerce in isInteger (e4e80f9)
+
+## 0.1.1
+
+* chore: trigger ci (b8ca7bd)
+* chore: tidy the example code (bb08e45)
+* fix: ensure sane grow behaviour for overflowed items (4e47f98)
+
+## 0.1.0
+
+* feature: implement basic circular array type (c17715a)
+* fix: throw from shift and unshift (660908f)
+* feature: add readme (8928671)
+* chore: add change log (cbb2b6b)
+* fix: fix lint error (d2b291a)
+
+## 0.0.0
+
+feat: add project boilerplate
+
Index: frontend/node_modules/hoopy/CONTRIBUTING.md
===================================================================
--- frontend/node_modules/hoopy/CONTRIBUTING.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/hoopy/CONTRIBUTING.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,28 @@
+# Contribution guidelines
+
+* Install/update the dependencies:
+  ```
+  npm i
+  ```
+
+* Make sure all of the tests pass:
+  ```
+  npm t
+  ```
+
+* Make sure the code lints:
+  ```
+  npm run lint
+  ```
+
+* Adhere to the coding conventions
+  that are used elsewhere in the codebase.
+
+* New code must have new unit tests.
+
+* New features
+  or changes to existing features
+  must be documented in the [readme file](README.md).
+
+* Add yourself to the [authors file](AUTHORS).
+
Index: frontend/node_modules/hoopy/LICENSE
===================================================================
--- frontend/node_modules/hoopy/LICENSE	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/hoopy/LICENSE	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,20 @@
+Copyright © 2017 Phil Booth
+
+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.
+
Index: frontend/node_modules/hoopy/README.md
===================================================================
--- frontend/node_modules/hoopy/README.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/hoopy/README.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,165 @@
+# hoopy
+
+[![Build status](https://gitlab.com/philbooth/hoopy/badges/master/pipeline.svg)](https://gitlab.com/philbooth/hoopy/pipelines)
+[![Package status](https://img.shields.io/npm/v/hoopy.svg)](https://www.npmjs.com/package/hoopy)
+[![Downloads](https://img.shields.io/npm/dm/hoopy.svg)](https://www.npmjs.com/package/hoopy)
+[![License](https://img.shields.io/npm/l/hoopy.svg)](https://opensource.org/licenses/MIT)
+
+
+Like an array, but rounder.
+
+* [Huh?](#huh)
+* [What's it useful for?](#whats-it-useful-for)
+* [How do I install it?](#how-do-i-install-it)
+* [How do I use it?](#how-do-i-use-it)
+  * [Loading the library](#loading-the-library)
+  * [Creating arrays](#creating-arrays)
+  * [Accessing array items](#accessing-array-items)
+  * [Growing the array](#growing-the-array)
+* [Is there a change log?](#is-there-a-change-log)
+* [How do I set up the dev environment?](#how-do-i-set-up-the-dev-environment)
+* [What license is it released under?](#what-license-is-it-released-under)
+
+## Huh?
+
+Hoopy is a circular array
+data type.
+It extends `Array`
+so that out-of-bounds indices
+wrap back round
+to the start of the array
+(or if they're negative indices,
+they wrap back round
+to the end of the array).
+
+## What's it useful for?
+
+If you want a fixed-length buffer
+for streamed I/O,
+Hoopy can do that for you.
+
+## How do I install it?
+
+Via `npm`:
+
+```
+npm i hoopy --save
+```
+
+Or if you just want the git repo:
+
+```
+git clone git@gitlab.com:philbooth/hoopy.git
+```
+
+## How do I use it?
+
+### Loading the library
+
+```js
+const Hoopy = require('hoopy');
+```
+
+### Creating arrays
+
+```js
+const hoopy = new Hoopy(10);
+assert(Array.isArray(hoopy));
+```
+
+You must pass
+a `size` argument
+to the `Hoopy` constructor,
+otherwise it will throw.
+
+### Accessing array items
+
+```js
+for (let i = 0; i < hoopy.length; ++i) {
+  hoopy[i] = i;
+  console.log(hoopy[i]);
+}
+```
+
+You can read and write array items
+using square brackets for indexing
+as you would with a normal array.
+However, if you write to
+an out-of-bounds index,
+it will not increase
+the length of the array.
+Instead the index is applied
+modulo the array length,
+wrapping back round to the start.
+Negative indices work in reverse,
+wrapping back round to the end
+of the array.
+
+The methods
+`push`, `pop`, `shift` and `unshift`
+will throw if called.
+Future versions of the library
+may implement sane behaviour
+for them.
+All of the other `Array` methods
+work normally.
+
+### Growing the array
+
+```js
+hoopy.grow(50);
+```
+
+The `grow` method
+adds items to the array.
+It takes one argument,
+which is the number
+of items to grow the array by.
+The new length of the array
+will be the old length
+plus the number you pass to `grow`.
+
+If the current state of the array
+includes overflowed indices,
+`grow` will take care
+to move those items
+in to the freshly-created
+available space,
+so that the correct order is maintained
+for your data.
+
+The caller is responsible
+for ensuring they don't overwrite
+unprocessed items.
+If you need to increase
+the size of the array,
+you must call `grow`.
+
+## Is there a change log?
+
+[Yes](CHANGELOG.md).
+
+## How do I set up the dev environment?
+
+To install the dependencies:
+
+```
+npm i
+```
+
+To run the tests:
+
+```
+npm t
+```
+
+To lint the code:
+
+```
+npm run lint
+```
+
+## What license is it released under?
+
+[MIT](LICENSE).
+
Index: frontend/node_modules/hoopy/index.js
===================================================================
--- frontend/node_modules/hoopy/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/hoopy/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,103 @@
+'use strict'
+
+class Hoopy extends Array {
+  constructor (size) {
+    let index, isIndexOverflowed
+
+    if (! isPositiveInteger(size)) {
+      throw new TypeError('Argument `size` must be a positive integer.')
+    }
+
+    super(size)
+
+    this.grow = by => {
+      if (! isPositiveInteger(by)) {
+        throw new TypeError('Argument `by` must be a positive integer.')
+      }
+
+      let i
+      const newSize = size + by
+
+      for (i = size; i < newSize; ++i) {
+        this[i] = undefined
+      }
+
+      if (isIndexOverflowed) {
+        for (i = 0; i <= index; ++i) {
+          let j = size + i
+          if (j >= newSize) {
+            j %= newSize
+          }
+          this[j] = this[i]
+          this[i] = undefined
+        }
+      }
+
+      size = newSize
+    }
+
+    return new Proxy(this, {
+      get (target, key) {
+        if (isInteger(key)) {
+          return target[getIndex(key, size)]
+        }
+
+        return target[key]
+      },
+
+      set (target, key, value) {
+        if (isInteger(key)) {
+          index = getIndex(key, size)
+          target[index] = value
+
+          if (Math.abs(key) >= size) {
+            isIndexOverflowed = true
+          } else {
+            isIndexOverflowed = false
+          }
+        } else {
+          target[key] = value
+        }
+        return true
+      }
+    })
+  }
+}
+
+function isPositiveInteger (thing) {
+  return isInteger(thing) && thing > 0
+}
+
+function isInteger (thing) {
+  try {
+    return +thing % 1 === 0
+  } catch (error) {
+    // Coercing symbols to numbers throws an error
+  }
+
+  return false
+}
+
+function getIndex (key, size) {
+  if (key === 0) {
+    return 0
+  }
+
+  if (key < 0) {
+    return (size - Math.abs(key)) % size
+  }
+
+  return key % size
+}
+
+function nop () {
+  throw new Error('Not implemented')
+}
+
+Hoopy.prototype.push = nop
+Hoopy.prototype.pop = nop
+Hoopy.prototype.shift = nop
+Hoopy.prototype.unshift = nop
+
+module.exports = Hoopy
+
Index: frontend/node_modules/hoopy/package.json
===================================================================
--- frontend/node_modules/hoopy/package.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/hoopy/package.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,39 @@
+{
+  "name": "hoopy",
+  "version": "0.1.4",
+  "license": "MIT",
+  "description": "Like an array, but rounder.",
+  "keywords": [
+    "data",
+    "structure",
+    "collection",
+    "array",
+    "circular",
+    "extends",
+    "proxy"
+  ],
+  "author": "Phil Booth <pmbooth@gmail.com> (https://philbooth.me/)",
+  "main": "index.js",
+  "engines": {
+    "node": ">= 6.0.0"
+  },
+  "scripts": {
+    "lint": "eslint .",
+    "test": "mocha --es_staging --ui tdd test.js",
+    "release": "release"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://gitlab.com/philbooth/hoopy.git"
+  },
+  "homepage": "https://gitlab.com/philbooth/hoopy#readme",
+  "bugs": {
+    "url": "https://gitlab.com/philbooth/hoopy/issues"
+  },
+  "devDependencies": {
+    "chai": "^4.1.2",
+    "eslint": "^3.19.0",
+    "mocha": "^5.2.0",
+    "please-release-me": "^2.0.2"
+  }
+}
Index: frontend/node_modules/hoopy/test.js
===================================================================
--- frontend/node_modules/hoopy/test.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/hoopy/test.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,311 @@
+/* eslint-env mocha */
+/* eslint-disable max-nested-callbacks */
+
+'use strict'
+
+const { assert } = require('chai')
+const Hoopy = require('.')
+
+test('interface is correct', () => {
+  assert.isFunction(Hoopy)
+  assert.lengthOf(Hoopy, 1)
+  assert.throws(() => new Hoopy(0))
+  assert.doesNotThrow(() => new Hoopy(1))
+  assert.throws(() => new Hoopy(-1))
+  assert.throws(() => new Hoopy(1).push())
+  assert.throws(() => new Hoopy(1).pop())
+  assert.throws(() => new Hoopy(1).shift())
+  assert.throws(() => new Hoopy(1).unshift())
+})
+
+suite('instantiate, size=1:', () => {
+  let hoopy
+
+  setup(() => {
+    hoopy = new Hoopy(1)
+  })
+
+  test('instance is array', () => {
+    assert.isTrue(Array.isArray(hoopy))
+  })
+
+  test('length is correct', () => {
+    assert.equal(hoopy.length, 1)
+  })
+
+  test('[0] is undefined', () => {
+    assert.isUndefined(hoopy[0])
+  })
+
+  test('[1] is undefined', () => {
+    assert.isUndefined(hoopy[1])
+  })
+
+  test('[-1] is undefined', () => {
+    assert.isUndefined(hoopy[-1])
+  })
+
+  test('grow method is implemented', () => {
+    assert.isFunction(hoopy.grow)
+    assert.lengthOf(hoopy.grow, 1)
+  })
+
+  test('grow throws if by=0', () => {
+    assert.throws(() => hoopy.grow(0))
+  })
+
+  suite('assign to [0]:', () => {
+    setup(() => {
+      hoopy[0] = 'foo'
+    })
+
+    test('[0] is set correctly', () => {
+      assert.equal(hoopy[0], 'foo')
+    })
+
+    test('[1] is set correctly', () => {
+      assert.equal(hoopy[1], 'foo')
+    })
+
+    test('[-1] is set correctly', () => {
+      assert.equal(hoopy[-1], 'foo')
+    })
+
+    suite('assign to [1]:', () => {
+      setup(() => {
+        hoopy[1] = 'bar'
+      })
+
+      test('[0] is set correctly', () => {
+        assert.equal(hoopy[0], 'bar')
+      })
+
+      test('[1] is set correctly', () => {
+        assert.equal(hoopy[1], 'bar')
+      })
+
+      test('[-1] is set correctly', () => {
+        assert.equal(hoopy[-1], 'bar')
+      })
+    })
+
+    suite('grow, by=1:', () => {
+      setup(() => {
+        hoopy.grow(1)
+      })
+
+      test('length is correct', () => {
+        assert.equal(hoopy.length, 2)
+      })
+
+      test('[0] is set correctly', () => {
+        assert.equal(hoopy[0], 'foo')
+      })
+
+      test('[1] is undefined', () => {
+        assert.isUndefined(hoopy[1])
+      })
+
+      test('[-1] is undefined', () => {
+        assert.isUndefined(hoopy[-1])
+      })
+    })
+  })
+})
+
+suite('instantiate, size=2:', () => {
+  let hoopy
+
+  setup(() => {
+    hoopy = new Hoopy(2)
+  })
+
+  test('length is correct', () => {
+    assert.equal(hoopy.length, 2)
+  })
+
+  suite('assign to [0]:', () => {
+    setup(() => {
+      hoopy[0] = 'foo'
+    })
+
+    test('[0] is set correctly', () => {
+      assert.equal(hoopy[0], 'foo')
+    })
+
+    test('[1] is undefined', () => {
+      assert.isUndefined(hoopy[1])
+    })
+
+    test('[2] is set correctly', () => {
+      assert.equal(hoopy[2], 'foo')
+    })
+
+    test('[3] is undefined', () => {
+      assert.isUndefined(hoopy[3])
+    })
+
+    test('[-1] is undefined', () => {
+      assert.isUndefined(hoopy[-1])
+    })
+
+    suite('assign to [1]:', () => {
+      setup(() => {
+        hoopy[1] = 'bar'
+      })
+
+      test('[0] is set correctly', () => {
+        assert.equal(hoopy[0], 'foo')
+      })
+
+      test('[1] is set correctly', () => {
+        assert.equal(hoopy[1], 'bar')
+      })
+
+      test('[2] is set correctly', () => {
+        assert.equal(hoopy[2], 'foo')
+      })
+
+      test('[-1] is set correctly', () => {
+        assert.equal(hoopy[-1], 'bar')
+      })
+
+      suite('assign to [2]:', () => {
+        setup(() => {
+          hoopy[2] = 'baz'
+        })
+
+        test('[0] is set correctly', () => {
+          assert.equal(hoopy[0], 'baz')
+        })
+
+        test('[1] is set correctly', () => {
+          assert.equal(hoopy[1], 'bar')
+        })
+
+        test('[2] is set correctly', () => {
+          assert.equal(hoopy[2], 'baz')
+        })
+
+        test('[-1] is set correctly', () => {
+          assert.equal(hoopy[-1], 'bar')
+        })
+
+        suite('grow, by=1:', () => {
+          setup(() => {
+            hoopy.grow(1)
+          })
+
+          test('length is correct', () => {
+            assert.equal(hoopy.length, 3)
+          })
+
+          test('[0] is undefined', () => {
+            assert.isUndefined(hoopy[0])
+          })
+
+          test('[1] is set correctly', () => {
+            assert.equal(hoopy[1], 'bar')
+          })
+
+          test('[2] is set correctly', () => {
+            assert.equal(hoopy[2], 'baz')
+          })
+
+          test('[3] is undefined', () => {
+            assert.isUndefined(hoopy[3])
+          })
+        })
+
+        suite('grow, by=2:', () => {
+          setup(() => {
+            hoopy.grow(2)
+          })
+
+          test('length is correct', () => {
+            assert.equal(hoopy.length, 4)
+          })
+
+          test('[0] is undefined', () => {
+            assert.isUndefined(hoopy[0])
+          })
+
+          test('[1] is set correctly', () => {
+            assert.equal(hoopy[1], 'bar')
+          })
+
+          test('[2] is set correctly', () => {
+            assert.equal(hoopy[2], 'baz')
+          })
+
+          test('[3] is undefined', () => {
+            assert.isUndefined(hoopy[3])
+          })
+
+          test('[4] is undefined', () => {
+            assert.isUndefined(hoopy[4])
+          })
+
+          test('[5] is set correctly', () => {
+            assert.equal(hoopy[5], 'bar')
+          })
+        })
+      })
+    })
+  })
+})
+
+suite('instantiate and overflow, size=3:', () => {
+  let hoopy
+
+  setup(() => {
+    hoopy = new Hoopy(3)
+    hoopy[2] = 'foo'
+    hoopy[3] = 'bar'
+    hoopy[4] = 'baz'
+  })
+
+  test('data is correct', () => {
+    assert.equal(hoopy.length, 3)
+    assert.equal(hoopy[2], 'foo')
+    assert.equal(hoopy[3], 'bar')
+    assert.equal(hoopy[4], 'baz')
+    assert.equal(hoopy[0], hoopy[3])
+    assert.equal(hoopy[1], hoopy[4])
+  })
+
+  test('slice works correctly', () => {
+    assert.equal(hoopy.slice(0, 3)[2], hoopy[2])
+  })
+
+  suite('grow, by=1:', () => {
+    setup(() => {
+      hoopy.grow(1)
+    })
+
+    test('data is correct', () => {
+      assert.equal(hoopy.length, 4)
+      assert.equal(hoopy[2], 'foo')
+      assert.equal(hoopy[3], 'bar')
+      assert.equal(hoopy[4], 'baz')
+      assert.equal(hoopy[0], hoopy[4])
+      assert.isUndefined(hoopy[1])
+    })
+  })
+
+  suite('grow, by=2:', () => {
+    setup(() => {
+      hoopy.grow(2)
+    })
+
+    test('data is correct', () => {
+      assert.equal(hoopy.length, 5)
+      assert.equal(hoopy[2], 'foo')
+      assert.equal(hoopy[3], 'bar')
+      assert.equal(hoopy[4], 'baz')
+      assert.isUndefined(hoopy[0])
+      assert.isUndefined(hoopy[1])
+    })
+  })
+})
+
