Repository: vigetlabs/microcosm
Pull request #255 (2017-03-13 03:41:39)

[#247] Update site README with more detailed instructions
Addresses 1 item of #247 

This PR adds more details on the contents of the `site` folder, more detailed instructions, and some background about technologies being used.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f2a479bd..a75fccb7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
 # Changelog
 
+## 12.1.3
+
+- Fix issue where pushing actions inside of other actions would cause
+  a rollback to initial state.
+
 ## 12.1.2
 
 - Fixed issue where parent repos would send out changes before
diff --git a/package.json b/package.json
index 1e42d738..c85315cb 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "microcosm",
-  "version": "12.1.2",
+  "version": "12.1.3",
   "private": true,
   "description": "Flux with actions at center stage. Write optimistic updates, cancel requests, and track changes with ease.",
   "main": "microcosm.js",
diff --git a/site/README.md b/site/README.md
index 5f807d9b..46e403c3 100644
--- a/site/README.md
+++ b/site/README.md
@@ -1,14 +1,42 @@
 # Microcosm Site
 
-The Microcosm website draws from `../docs` and `./src/content` to
-build pages. It is published to Github Pages here:
+- [Introduction](#introduction)
+- [Setup](#setup)
+- [Run Locally](#run-locally)
+- [Publish](#publish)
 
-http://code.viget.com/microcosm/
+## Introduction
+
+The Microcosm website is built using markdown files, html templates, a stylesheet, and a few image assets. It is published using Github pages on the `gh-pages` branch. Check out the live site here: http://code.viget.com/microcosm/
+
+> For more information on Gihub Pages got here: https://pages.github.com/.
+
+Here is a brief overview of everything you'll find in the `site` folder:
+
+### public
+
+The generated site will live here after running (we'll go over generating the site and serving the site locally in the next section.) With the exception of the `assets` folder you should never edit these files, they will be overwritten when regenerating the site and aren't in source control.
+
+### scripts
+
+The `Makefile` uses these files to generate the html files in the `public` folder, to serve the site locally, and to publish the site to Github pages.
+
+### src
+
+The site is generated using the markdown files in `./src/pages` and the html templates in `.src/layouts` by Pandoc (see [setup section below](#setup)).
+
+> Note that markdown files can also be found in `../docs` outside the site folder.
+
+### Makefile
+
+A [Makefile](https://en.wikipedia.org/wiki/Makefile) provides a few easy commands for building, serving, and publishing the site. To keep things simple there are two main ones you will most likely need:
+
+- `make server` which lets you run the site locally on `localhost:4000`
+- `make publish` which bundles the site, puts the files on the `gh-pages` branch, and pushes them to Github
 
 ## Setup
 
-We use [pandoc](pandoc.org) to generate the site. This
-needs to be installed. For OSX users:
+We use [pandoc](pandoc.org) to generate the site. This needs to be installed. For OS X users:
 
 ```
 brew install pandoc
@@ -23,11 +51,17 @@ cd site
 make server
 ```
 
+The html files will be generated (or updated) into `./public` and a server will be started running on `localhost:4000`. Point your browser there to see the site. Changes to files in `./src` or any `.md` file will trigger a live reload on the site (no need to hit refresh).
+
+> To see what is going on under the hood look at the `Makefile` and at the `./scripts/serve` script. If you are not familiar with browser-sync check out [their website](https://www.browsersync.io/) for details.
+
 ## Publish
 
-From the project root:
+Publishing to `http://code.viget.com/microcosm` can only be done by core contributors with merging privileges. If this is you then publishing is straightforward, from the project root:
 
 ```
 cd site
 make publish
 ```
+
+> To see what is going on under the hood look at the `Makefile` and at the `./scripts/publish` script. The script will force push the site to the `gh-pages` branch which will publish the site to `http://code.viget.com/microcosm`
diff --git a/src/history.js b/src/history.js
index ee9bee4a..d0678a88 100644
--- a/src/history.js
+++ b/src/history.js
@@ -84,6 +84,8 @@ History.prototype = {
     this.head = action
     this.size += 1
 
+    this.invoke('createInitialSnapshot', action)
+
     return this.head
   },
 
@@ -105,7 +107,7 @@ History.prototype = {
 
     this.archive()
 
-    this.invoke('update')
+    this.invoke('prepareRelease')
 
     this.invoke('release', action)
   },
@@ -116,7 +118,7 @@ History.prototype = {
 
     while (size > this.limit && root.disposable) {
       size -= 1
-      this.invoke('clean', root.parent)
+      this.invoke('removeSnapshot', root.parent)
       root = root.next
     }
 
diff --git a/src/lifecycle.js b/src/lifecycle.js
index c96ba385..f3f427ae 100644
--- a/src/lifecycle.js
+++ b/src/lifecycle.js
@@ -16,14 +16,18 @@ function sandbox (data, deserialize) {
   }
 }
 
-export const RESET = tag(function reset (data, deserialize) {
+export const RESET = tag(function $reset (data, deserialize) {
   return sandbox(data, deserialize)
 }, '$reset')
 
-export const PATCH = tag(function patch (data, deserialize) {
+export const PATCH = tag(function $patch (data, deserialize) {
   return sandbox(data, deserialize)
 }, '$patch')
 
-export const BIRTH = '$birth'
+export const BIRTH = function $birth () {
+  console.assert(false, 'Birth lifecycle method should never be invoked directly.')
+}
 
-export const START = '$start'
+export const START = function $start () {
+  console.assert(false, 'Start lifecycle method should never be invoked directly.')
+}
diff --git a/src/microcosm.js b/src/microcosm.js
index b43fae9c..e8a4ac68 100644
--- a/src/microcosm.js
+++ b/src/microcosm.js
@@ -78,19 +78,36 @@ inherit(Microcosm, Emitter, {
 
   recall (action) {
     console.assert(action, 'Unable to get ' + typeof action + ' action')
+
     return this.archive.get(action.id)
   },
 
-  save (action, state) {
-    console.assert(action, 'Unable to set ' + typeof action + ' action.')
-    console.assert(action.command !== BIRTH, 'Birth action should never be set.')
+  /**
+   * Create the initial state snapshot for an action. This is
+   * important so that, when rolling back to this action, it always
+   * has a state value.
+   * @param {Action} action - The action to generate a snapshot for
+   */
+  createInitialSnapshot (action) {
+    let state = this.recall(action.parent)
 
-    return this.archive.set(action.id, state)
+    this.updateSnapshot(action, state)
   },
 
-  clean (action) {
+  /**
+   * Update the state snapshot for a given action
+   * @param {Action} action - The action to update the snapshot for
+   */
+  updateSnapshot (action, state) {
+    this.archive.set(action.id, state)
+  },
+
+  /**
+   * Remove the snapshot for a given action
+   * @param {Action} action - The action to remove the snapshot for
+   */
+  removeSnapshot (action) {
     console.assert(action, 'Unable to remove ' + typeof action + ' action.')
-    console.assert(action.command !== BIRTH, 'Birth action should never be removed.')
 
     this.archive.remove(action.id)
   },
@@ -126,12 +143,12 @@ inherit(Microcosm, Emitter, {
       next = this.dispatch(next, action)
     }
 
-    this.save(action, next)
+    this.updateSnapshot(action, next)
 
     return this
   },
 
-  update () {
+  prepareRelease () {
     let next = this.follower ? this.parent.state : this.recall(this.history.head)
 
     this.dirty = next !== this.state


Test patch:
diff --git a/test/integration/nested-actions.test.js b/test/integration/nested-actions.test.js
new file mode 100644
index 00000000..72db768c
--- /dev/null
+++ b/test/integration/nested-actions.test.js
@@ -0,0 +1,63 @@
+/**
+ * This test covers a bug where the state gets out of sync when an
+ * action is pushed within another action. We first encountered this
+ * on a project where we wanted to remove a "focused" state when saving
+ * data using another action.
+ */
+
+import Microcosm, { update } from '../../src/microcosm'
+
+describe('When pushing actions inside of another action', function () {
+
+  it('does not return to an old state when pushing the second action', function () {
+    expect.assertions(8)
+
+    let repo = new Microcosm()
+
+    function focuser (focus) {
+      return focus
+    }
+
+    function stepper () {
+      return function (action, repo) {
+        expect(repo).toHaveState('data.count', 2)
+        expect(repo).toHaveState('data.focus', true)
+
+        repo.push(focuser, false)
+
+        expect(repo).toHaveState('data.count', 2)
+        expect(repo).toHaveState('data.focus', false)
+
+        action.open()
+        expect(repo).toHaveState('data.count', 2)
+        expect(repo).toHaveState('data.focus', false)
+
+        action.resolve(1)
+        expect(repo).toHaveState('data.count', 3)
+        expect(repo).toHaveState('data.focus', false)
+      }
+    }
+
+    repo.addDomain('data', {
+      getInitialState () {
+        return { count: 0, focus: false }
+      },
+      increase (state, amount) {
+        return update(state, 'count', n => n + amount)
+      },
+      setFocus (state, yes) {
+        return update(state, 'focus', yes)
+      },
+      register () {
+        return {
+          [stepper]: this.increase,
+          [focuser]: this.setFocus
+        }
+      }
+    })
+
+    repo.patch({ data: { count: 2, focus: true } })
+    repo.push(stepper)
+  })
+
+})
diff --git a/test/unit/microcosm/lifecycle.test.js b/test/unit/microcosm/lifecycle.test.js
new file mode 100644
index 00000000..a45acaec
--- /dev/null
+++ b/test/unit/microcosm/lifecycle.test.js
@@ -0,0 +1,24 @@
+import Microcosm from '../../../src/microcosm'
+
+import {
+  BIRTH,
+  START
+} from '../../../src/lifecycle'
+
+describe('Lifecycle', function () {
+
+  it('$birth should never be invoked directly', function () {
+    let repo = new Microcosm()
+    let test = repo.prepare(BIRTH)
+
+    expect(test).toThrow()
+  })
+
+  it('$start should never be invoked directly', function () {
+    let repo = new Microcosm()
+    let test = repo.prepare(START)
+
+    expect(test).toThrow()
+  })
+
+})