Repository: vigetlabs/microcosm
Pull request #320 (2017-05-12 11:43:44)

Presenter model callbacks maintain scope, pass repo
This PR does two things: 

1. Model callbacks are invoked within the scope of the presenter
2. The current repo is passed as the second argument

#### Model callbacks are invoked within the scope of the presenter

Context otherwise gets lost when you pass a reference to a Presenter method in getModel:

```javascript
class MyPresenter extends Presenter {
  getModel(props) {
    return {
      key: this.process
    }
  }
  
  process () {
    // You'd expect `this` to be the presenter, right?
  }
}
```

#### The current repo is passed as the second argument

This lets you do neat things, like push actions if data is missing

```javascript
function getUsers (state, repo) {
  let users = get(state, 'users', [])

  if (users.length <= 0) {
    repo.push(getUsers)
  }

  return users
}

class UsersPresenter extends Presenter {
  getModel(props) {
    return {
      users: getUsers
    }
  }
}
```

There's some more nuance to that sort of thing to prevent over-fetching, but the basic principle still applies. Passing `repo` as the second argument of model callbacks will let us do some pretty neat stuff.

codecov-io: # [Codecov](https://codecov.io/gh/vigetlabs/microcosm/pull/320?src=pr&el=h1) Report
> Merging [#320](https://codecov.io/gh/vigetlabs/microcosm/pull/320?src=pr&el=desc) into [master](https://codecov.io/gh/vigetlabs/microcosm/commit/8be46b8ea8fedfac89e948b987e7b805175b1378?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/vigetlabs/microcosm/pull/320/graphs/tree.svg?token=AaE0FLz9sU&src=pr&height=150&width=650)](https://codecov.io/gh/vigetlabs/microcosm/pull/320?src=pr&el=tree)

```diff
@@          Coverage Diff          @@
##           master   #320   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files          23     23           
  Lines         787    787           
=====================================
  Hits          787    787
```


| [Impacted Files](https://codecov.io/gh/vigetlabs/microcosm/pull/320?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/addons/presenter.js](https://codecov.io/gh/vigetlabs/microcosm/pull/320?src=pr&el=tree#diff-c3JjL2FkZG9ucy9wcmVzZW50ZXIuanM=) | `100% <100%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/vigetlabs/microcosm/pull/320?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/vigetlabs/microcosm/pull/320?src=pr&el=footer). Last update [8be46b8...da0db8a](https://codecov.io/gh/vigetlabs/microcosm/pull/320?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/src/addons/presenter.js b/src/addons/presenter.js
index be2387d2..95423ed6 100644
--- a/src/addons/presenter.js
+++ b/src/addons/presenter.js
@@ -254,7 +254,7 @@ class PresenterMediator extends React.PureComponent {
 
       if (typeof entry === 'function') {
         this.propMap[key] = entry
-        next[key] = entry(data)
+        next[key] = entry.call(this.presenter, data, this.repo)
       } else {
         next[key] = entry
       }
@@ -270,7 +270,7 @@ class PresenterMediator extends React.PureComponent {
     let next = null
 
     for (var key in this.propMap) {
-      var value = this.propMap[key](state)
+      var value = this.propMap[key].call(this.presenter, state, this.repo)
 
       if (last[key] !== value) {
         next = next || {}


Test patch:
diff --git a/test/addons/presenter.test.js b/test/addons/presenter.test.js
index 315d3ddd..01dca4ec 100644
--- a/test/addons/presenter.test.js
+++ b/test/addons/presenter.test.js
@@ -164,6 +164,46 @@ describe('::getModel', function() {
     expect(spy).toHaveBeenCalledTimes(1)
   })
 
+  describe('when first building a model', function() {
+    it('passes the repo as the second argument of model callbacks', function() {
+      expect.assertions(1)
+
+      let repo = new Repo()
+
+      class TestCase extends Presenter {
+        getModel(props) {
+          return {
+            name: this.process
+          }
+        }
+
+        process(_state, fork) {
+          expect(fork.parent).toBe(repo)
+        }
+      }
+
+      mount(<TestCase repo={repo} />)
+    })
+
+    it('invokes model callbacks in the scope of the presenter', function() {
+      expect.assertions(1)
+
+      class TestCase extends Presenter {
+        getModel(props) {
+          return {
+            name: this.process
+          }
+        }
+
+        process() {
+          expect(this).toBeInstanceOf(TestCase)
+        }
+      }
+
+      mount(<TestCase />)
+    })
+  })
+
   describe('when updating props', function() {
     it('recalculates the view model if the props are different', function() {
       const repo = new Microcosm()
@@ -206,6 +246,50 @@ describe('::getModel', function() {
 
       expect(spy.mock.calls.length).toEqual(1)
     })
+
+    it('passes the repo as the second argument of model callbacks', function() {
+      expect.assertions(2)
+
+      let repo = new Repo()
+
+      class TestCase extends Presenter {
+        getModel(props) {
+          return {
+            name: this.process
+          }
+        }
+
+        process(_state, fork) {
+          expect(fork.parent).toBe(repo)
+        }
+      }
+
+      mount(<TestCase repo={repo} />)
+
+      repo.patch({ color: 'purple' })
+    })
+
+    it('invokes model callbacks in the scope of the presenter', function() {
+      expect.assertions(2)
+
+      let repo = new Repo()
+
+      class TestCase extends Presenter {
+        getModel(props) {
+          return {
+            name: this.process
+          }
+        }
+
+        process() {
+          expect(this).toBeInstanceOf(TestCase)
+        }
+      }
+
+      mount(<TestCase repo={repo} />)
+
+      repo.patch({ color: 'purple' })
+    })
   })
 
   describe('when updating state', function() {