diff --git a/src/js/service/HistoryService.js b/src/js/service/HistoryService.js index 973f428..62b84e3 100644 --- a/src/js/service/HistoryService.js +++ b/src/js/service/HistoryService.js @@ -1,12 +1,10 @@ (function () { var ns = $.namespace('pskl.service'); - var SNAPSHOT_PERIOD = 50; - var LOAD_STATE_INTERVAL = 50; - - ns.HistoryService = function (piskelController, shortcutService) { - this.piskelController = piskelController || pskl.app.piskelController; - this.shortcutService = shortcutService || pskl.app.shortcutService; + ns.HistoryService = function (piskelController, shortcutService, deserializer) { + this.$piskelController = piskelController || pskl.app.piskelController; + this.$shortcutService = shortcutService || pskl.app.shortcutService; + this.$deserializer = deserializer || pskl.utils.serialization.Deserializer; this.stateQueue = []; this.currentIndex = -1; @@ -18,6 +16,8 @@ ns.HistoryService.SNAPSHOT = 'SNAPSHOT'; ns.HistoryService.REPLAY = 'REPLAY'; + ns.HistoryService.SNAPSHOT_PERIOD = 50; + ns.HistoryService.LOAD_STATE_INTERVAL = 50; /** * This event alters the state (frames, layers) of the piskel. The event is triggered before the execution of associated command. * Don't store snapshots for such events. @@ -27,8 +27,8 @@ ns.HistoryService.prototype.init = function () { $.subscribe(Events.PISKEL_SAVE_STATE, this.onSaveStateEvent.bind(this)); - this.shortcutService.addShortcut('ctrl+Z', this.undo.bind(this)); - this.shortcutService.addShortcut('ctrl+Y', this.redo.bind(this)); + this.$shortcutService.addShortcut('ctrl+Z', this.undo.bind(this)); + this.$shortcutService.addShortcut('ctrl+Y', this.redo.bind(this)); this.saveState({ type : ns.HistoryService.SNAPSHOT @@ -45,17 +45,17 @@ var state = { action : stateInfo, - frameIndex : this.piskelController.currentFrameIndex, - layerIndex : this.piskelController.currentLayerIndex + frameIndex : this.$piskelController.currentFrameIndex, + layerIndex : this.$piskelController.currentLayerIndex }; var isSnapshot = stateInfo.type === ns.HistoryService.SNAPSHOT; var isNoSnapshot = stateInfo.type === ns.HistoryService.REPLAY_NO_SNAPSHOT; - var isAtAutoSnapshotInterval = this.currentIndex % SNAPSHOT_PERIOD === 0 || this.saveNextAsSnapshot; + var isAtAutoSnapshotInterval = this.currentIndex % ns.HistoryService.SNAPSHOT_PERIOD === 0 || this.saveNextAsSnapshot; if (isNoSnapshot && isAtAutoSnapshotInterval) { this.saveNextAsSnapshot = true; } else if (isSnapshot || isAtAutoSnapshotInterval) { - state.piskel = this.piskelController.serialize(true); + state.piskel = this.$piskelController.serialize(true); this.saveNextAsSnapshot = false; } @@ -71,7 +71,7 @@ }; ns.HistoryService.prototype.isLoadStateAllowed_ = function (index) { - var timeOk = (Date.now() - this.lastLoadState) > LOAD_STATE_INTERVAL; + var timeOk = (Date.now() - this.lastLoadState) > ns.HistoryService.LOAD_STATE_INTERVAL; var indexInRange = index >= 0 && index < this.stateQueue.length; return timeOk && indexInRange; }; @@ -94,7 +94,7 @@ } var serializedPiskel = this.getSnapshotFromState_(snapshotIndex); var onPiskelLoadedCb = this.onPiskelLoaded_.bind(this, index, snapshotIndex); - pskl.utils.serialization.Deserializer.deserialize(serializedPiskel, onPiskelLoadedCb); + this.$deserializer.deserialize(serializedPiskel, onPiskelLoadedCb); } } catch (e) { window.console.error("[CRITICAL ERROR] : Unable to load a history state."); @@ -126,8 +126,8 @@ ns.HistoryService.prototype.onPiskelLoaded_ = function (index, snapshotIndex, piskel) { var originalSize = this.getPiskelSize_(); - piskel.setDescriptor(this.piskelController.piskel.getDescriptor()); - this.piskelController.setPiskel(piskel); + piskel.setDescriptor(this.$piskelController.piskel.getDescriptor()); + this.$piskelController.setPiskel(piskel); for (var i = snapshotIndex + 1 ; i <= index ; i++) { var state = this.stateQueue[i]; @@ -135,6 +135,7 @@ this.replayState(state); } + // Should only do this when going backwards var lastState = this.stateQueue[index+1]; if (lastState) { this.setupState(lastState); @@ -148,18 +149,18 @@ }; ns.HistoryService.prototype.getPiskelSize_ = function () { - return this.piskelController.getWidth() + 'x' + this.piskelController.getHeight(); + return this.$piskelController.getWidth() + 'x' + this.$piskelController.getHeight(); }; ns.HistoryService.prototype.setupState = function (state) { - this.piskelController.setCurrentFrameIndex(state.frameIndex); - this.piskelController.setCurrentLayerIndex(state.layerIndex); + this.$piskelController.setCurrentFrameIndex(state.frameIndex); + this.$piskelController.setCurrentLayerIndex(state.layerIndex); }; ns.HistoryService.prototype.replayState = function (state) { var action = state.action; var type = action.type; - var layer = this.piskelController.getLayerAt(state.layerIndex); + var layer = this.$piskelController.getLayerAt(state.layerIndex); var frame = layer.getFrameAt(state.frameIndex); action.scope.replay(frame, action.replay); }; diff --git a/test/js/service/HistoryServiceTest.js b/test/js/service/HistoryServiceTest.js index f201f41..15827bb 100644 --- a/test/js/service/HistoryServiceTest.js +++ b/test/js/service/HistoryServiceTest.js @@ -1,22 +1,86 @@ -describe("History Service suite", function() { - it("starts at -1", function() { - var mockPiskelController = {}; - var mockShortcutService = {}; - var historyService = new pskl.service.HistoryService(mockPiskelController, mockShortcutService); - expect(historyService.currentIndex).toBe(-1); - }); +var callFactory = function (method) { + return { + times : function (times) { + var results = []; + for (var i = 0 ; i < times ; i++) { + results.push(method()); + } + return results; + }, + once : function () { + return method(); + } + }; +}; - it("is at 0 after init", function() { +describe("History Service suite", function() { + var SERIALIZED_PISKEL = 'serialized-piskel'; + var historyService = null; + + var getLastState = function () { + return historyService.stateQueue[historyService.currentIndex]; + }; + + var createMockHistoryService = function () { var mockPiskelController = { serialize : function () { - return 'serialized-piskel'; + return SERIALIZED_PISKEL; } }; var mockShortcutService = { addShortcut : function () {} }; - var historyService = new pskl.service.HistoryService(mockPiskelController, mockShortcutService); + return new pskl.service.HistoryService(mockPiskelController, mockShortcutService); + }; + + it("starts at -1", function() { + historyService = createMockHistoryService(); + expect(historyService.currentIndex).toBe(-1); + }); + + it("is at 0 after init", function() { + historyService = createMockHistoryService(); historyService.init(); expect(historyService.currentIndex).toBe(0); }); + + var sendSaveEvents = function (type) { + return callFactory (function () { + $.publish(Events.PISKEL_SAVE_STATE, { + type : type, + scope : {}, + replay : {} + }); + }); + }; + + it("stores a piskel snapshot after 5 SAVE", function () { + // BEFORE + var SNAPSHOT_PERIOD_BACKUP = pskl.service.HistoryService.SNAPSHOT_PERIOD; + pskl.service.HistoryService.SNAPSHOT_PERIOD = 5; + + historyService = createMockHistoryService(); + historyService.init(); + + sendSaveEvents(pskl.service.HistoryService.REPLAY).times(5); + + expect(historyService.currentIndex).toBe(5); + + expect(getLastState().piskel).toBe(SERIALIZED_PISKEL); + + sendSaveEvents(pskl.service.HistoryService.REPLAY).times(4); + + sendSaveEvents(pskl.service.HistoryService.REPLAY_NO_SNAPSHOT).once(); + expect(getLastState().piskel).toBeUndefined(); + + sendSaveEvents(pskl.service.HistoryService.REPLAY_NO_SNAPSHOT).once(); + expect(getLastState().piskel).toBeUndefined(); + + sendSaveEvents(pskl.service.HistoryService.REPLAY).once(); + expect(getLastState().piskel).toBe(SERIALIZED_PISKEL); + + // AFTER + pskl.service.HistoryService.SNAPSHOT_PERIOD = SNAPSHOT_PERIOD_BACKUP; + + }) }); \ No newline at end of file