summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJules Laplace <jules@okfoc.us>2014-08-08 18:56:41 -0400
committerJules Laplace <jules@okfoc.us>2014-08-08 18:56:41 -0400
commit9cd88d59e45b530e483490804503e6b47030fd4d (patch)
treebea17844d38188ab1f72bc3dcdbf6790035e691e
parent5ee6e8bc71861a2ab7c91a8289107e78fd216fc2 (diff)
undo stack
-rw-r--r--public/assets/javascripts/rectangles/util/undo.js42
-rw-r--r--test/00-setup.js1
-rw-r--r--test/09-test-undo.js163
3 files changed, 205 insertions, 1 deletions
diff --git a/public/assets/javascripts/rectangles/util/undo.js b/public/assets/javascripts/rectangles/util/undo.js
new file mode 100644
index 0000000..3700817
--- /dev/null
+++ b/public/assets/javascripts/rectangles/util/undo.js
@@ -0,0 +1,42 @@
+(function(){
+
+ var UndoStack = function(){
+ this.stack = []
+ this.types = {}
+ this.pointer = -1
+ }
+ UndoStack.prototype.push = function(action){
+ this.pointer++
+ this.stack[this.pointer] = action
+ this.purge()
+ }
+ UndoStack.prototype.purge = function(){
+ if (this.stack.length-1 == this.pointer) return
+ this.stack.length = this.pointer+1
+ }
+ UndoStack.prototype.undo = function(){
+ if (this.pointer == -1) return false
+ var action = this.stack[this.pointer]
+ this.types[ action.type ].undo(action)
+ this.pointer--
+ return this.pointer > -1
+ }
+ UndoStack.prototype.redo = function(){
+ if (this.pointer == this.stack.length-1) return false
+ this.pointer++
+ var action = this.stack[this.pointer]
+ this.types[ action.type ].redo(action)
+ return this.pointer < this.stack.length-1
+ }
+ UndoStack.prototype.register = function(actionType){
+ this.types[ actionType.type ] = actionType
+ }
+
+ if ('window' in this) {
+ window.UndoStack = new UndoStack
+ }
+ else {
+ module.exports = new UndoStack
+ }
+
+})()
diff --git a/test/00-setup.js b/test/00-setup.js
index 78ad2c4..20f9d66 100644
--- a/test/00-setup.js
+++ b/test/00-setup.js
@@ -1,2 +1 @@
Error.stackTraceLimit = 5
-
diff --git a/test/09-test-undo.js b/test/09-test-undo.js
new file mode 100644
index 0000000..84b5d09
--- /dev/null
+++ b/test/09-test-undo.js
@@ -0,0 +1,163 @@
+var assert = require("assert")
+var UndoStack = require("../public/assets/javascripts/rectangles/util/undo.js")
+
+describe('undo', function(){
+
+ var state = "zero"
+
+ describe('#register()', function(){
+
+ UndoStack.register({
+ type: "demo",
+ undo: function(action){
+ state = action.prev
+ },
+ redo: function(action){
+ state = action.next
+ },
+ })
+
+ it('registers undoable actions', function(){
+ assert( UndoStack.types.hasOwnProperty("demo") )
+ })
+ })
+
+ describe('#push()', function(){
+
+ it('starts empty', function(){
+ assert.equal(0, UndoStack.stack.length)
+ assert.equal(-1, UndoStack.pointer)
+ })
+
+ it('pushes some actions', function(){
+
+ UndoStack.push({
+ type: "demo",
+ prev: state,
+ next: "one"
+ })
+ state = "one"
+
+ UndoStack.push({
+ type: "demo",
+ prev: state,
+ next: "two"
+ })
+ state = "two"
+
+ UndoStack.push({
+ type: "demo",
+ prev: state,
+ next: "three"
+ })
+ state = "three"
+
+ assert.equal(3, UndoStack.stack.length)
+ assert.equal(2, UndoStack.pointer)
+ })
+ })
+
+ describe('#undo()', function(){
+
+ it('retrieves old state', function(){
+ assert.equal("three", state)
+ UndoStack.undo()
+ assert.equal("two", state)
+ assert.equal(1, UndoStack.pointer)
+ })
+
+ it('can only undo so far', function(){
+ var canUndo
+
+ canUndo = UndoStack.undo()
+ assert.equal("one", state)
+ assert.equal(0, UndoStack.pointer)
+ assert.equal(true, canUndo)
+
+ canUndo = UndoStack.undo()
+ assert.equal("zero", state)
+ assert.equal(-1, UndoStack.pointer)
+ assert.equal(false, canUndo)
+
+ canUndo = UndoStack.undo()
+ assert.equal("zero", state)
+ assert.equal(-1, UndoStack.pointer)
+ assert.equal(false, canUndo)
+ })
+
+ })
+
+ describe('#redo()', function(){
+
+ it('reassigns new state', function(){
+ UndoStack.redo()
+ assert.equal("one", state)
+ })
+
+ it('can only redo so far', function(){
+ var canRedo
+
+ canRedo = UndoStack.redo()
+ assert.equal("two", state)
+ assert.equal(true, canRedo)
+
+ canRedo = UndoStack.redo()
+ assert.equal("three", state)
+ assert.equal(false, canRedo)
+
+ canRedo = UndoStack.redo()
+ assert.equal("three", state)
+ assert.equal(false, canRedo)
+ })
+
+ it('clobbers old state if we undo then do something else', function(){
+ UndoStack.undo()
+ assert.equal("two", state)
+ assert.equal(1, UndoStack.pointer)
+
+ UndoStack.undo()
+ assert.equal("one", state)
+ assert.equal(0, UndoStack.pointer)
+ assert.equal(3, UndoStack.stack.length)
+
+ UndoStack.push({
+ type: "demo",
+ prev: state,
+ next: "four"
+ })
+ state = "four"
+
+ assert.equal(1, UndoStack.pointer)
+ assert.equal(2, UndoStack.stack.length)
+
+ UndoStack.undo()
+ assert.equal("one", state)
+ assert.equal(0, UndoStack.pointer)
+ assert.equal(2, UndoStack.stack.length)
+
+ UndoStack.redo()
+ assert.equal("four", state)
+ assert.equal(1, UndoStack.pointer)
+ assert.equal(2, UndoStack.stack.length)
+ })
+ })
+
+})
+
+// 1) push action
+// 2) push action
+// 3) push action
+// undo 3
+// undo 2
+// undo 1
+// undo * can't undo anymore
+// redo 1
+// redo 2
+// redo 3
+// redo * can't redo anymore
+// undo 3
+// 4) push action (clobbers action 3)
+// undo 4
+// undo 2
+// redo 2
+// redo 4