Skip to content

Instantly share code, notes, and snippets.

@BellAppLab
Created February 26, 2018 20:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BellAppLab/2f63d5911a6b2258a4fda877c00a7e93 to your computer and use it in GitHub Desktop.
Save BellAppLab/2f63d5911a6b2258a4fda877c00a7e93 to your computer and use it in GitHub Desktop.
Testing Apple's Undo Manager
//LE TEST
func testUndoManager() {
XCTAssertFalse(calculation.undoManager.canUndo, "A new calculation shouldn't be able to undo")
calculation.add(number: [.one])
XCTAssertTrue(calculation.undoManager.canUndo, "The calculation should be able to undo")
XCTAssertEqual(calculation.undoManager.undoMenuItemTitle, "Undo Insert 1", "Undo message is wrong")
calculation.undoManager.undo()
XCTAssertEqual(calculation.result, "0", "Calculation result should be zero, because we have undone everything")
XCTAssertFalse(calculation.undoManager.canUndo, "A clear calculation shouldn't be able to undo")
XCTAssertTrue(calculation.undoManager.canRedo, "A calculation that has been undone should be able to redo")
XCTAssertEqual(calculation.undoManager.redoMenuItemTitle, "Redo Insert 1", "Redo message is wrong")
calculation.clear()
calculation.add(number: [.one])
calculation.removeLastNumber()
XCTAssertEqual(calculation.result, "0", "Calculation result should be zero, because we have removeds everything")
XCTAssertTrue(calculation.undoManager.canUndo, "We should be able to undo")
XCTAssertFalse(calculation.undoManager.canRedo, "We shouldn't be able to redo here")
XCTAssertEqual(calculation.undoManager.undoMenuItemTitle, "Undo Remove 1", "Undo message is wrong")
calculation.undoManager.undo()
XCTAssertEqual(calculation.result, "1", "Calculation result should be 1, because we have undone everything")
XCTAssertTrue(calculation.undoManager.canUndo, "We should be able to undo")
XCTAssertTrue(calculation.undoManager.canRedo, "We should be able to redo here")
calculation.clear()
calculation.add(number: [.one])
calculation.add(number: [.two])
calculation.add(number: [.three])
calculation.add(number: [.four])
XCTAssertEqual(calculation.result, "4", "Calculation result should be 4")
XCTAssertTrue(calculation.undoManager.canUndo, "We should be able to undo")
XCTAssertFalse(calculation.undoManager.canRedo, "We shouldn't be able to redo here")
XCTAssertEqual(calculation.undoManager.undoMenuItemTitle, "Undo Insert 4", "Undo message is wrong")
calculation.undoManager.undo()
XCTAssertEqual(calculation.result, "3", "Calculation result should be 3")
XCTAssertTrue(calculation.undoManager.canUndo, "We should be able to undo")
XCTAssertTrue(calculation.undoManager.canRedo, "We should be able to redo here")
calculation.undoManager.undo()
XCTAssertEqual(calculation.result, "2", "Calculation result should be 2")
XCTAssertTrue(calculation.undoManager.canUndo, "We should be able to undo")
XCTAssertTrue(calculation.undoManager.canRedo, "We should be able to redo here")
calculation.undoManager.undo()
XCTAssertEqual(calculation.result, "1", "Calculation result should be 1")
XCTAssertTrue(calculation.undoManager.canUndo, "We should be able to undo")
XCTAssertTrue(calculation.undoManager.canRedo, "We should be able to redo here")
calculation.undoManager.undo()
XCTAssertEqual(calculation.result, "0", "Calculation result should be 0")
XCTAssertFalse(calculation.undoManager.canUndo, "We shouldn't be able to undo anymore")
XCTAssertTrue(calculation.undoManager.canRedo, "We should be able to redo here")
calculation.undoManager.redo()
XCTAssertEqual(calculation.result, "1", "Calculation result should be 1")
XCTAssertTrue(calculation.undoManager.canUndo, "We should be able to undo")
XCTAssertTrue(calculation.undoManager.canRedo, "We should be able to redo here")
calculation.undoManager.redo()
XCTAssertEqual(calculation.result, "2", "Calculation result should be 2")
XCTAssertTrue(calculation.undoManager.canUndo, "We should be able to undo")
XCTAssertTrue(calculation.undoManager.canRedo, "We should be able to redo here")
calculation.undoManager.redo()
XCTAssertEqual(calculation.result, "3", "Calculation result should be 3")
XCTAssertTrue(calculation.undoManager.canUndo, "We should be able to undo")
XCTAssertTrue(calculation.undoManager.canRedo, "We should be able to redo here")
calculation.undoManager.redo()
XCTAssertEqual(calculation.result, "4", "Calculation result should be 4")
XCTAssertTrue(calculation.undoManager.canUndo, "We should be able to undo here")
XCTAssertFalse(calculation.undoManager.canRedo, "We shouldn't be able to redo anymore")
}
}
//LE CODE
//MARK: - Undo Manager
extension Calculation
{
fileprivate func registerUndoAdd(number: Digits) {
self.registerUndo()
undoManager.beginUndoGrouping()
undoManager.registerUndo(withTarget: self, selector: #selector(undo), object: nil)
if let last = number.last {
switch last {
case .decimal:
undoManager.setActionName("\(NSLocalizedString("Insert", comment: "")) \(last.value)")
return
default:
break
}
}
undoManager.setActionName("\(NSLocalizedString("Insert", comment: "")) \(number.formatted)")
undoManager.endUndoGrouping()
}
fileprivate func registerUndoAdd(operation: Operator) {
self.registerUndo()
undoManager.beginUndoGrouping()
undoManager.registerUndo(withTarget: self, selector: #selector(undo), object: nil)
undoManager.setActionName("\(NSLocalizedString("Insert", comment: "")) \(operation.localizedName)")
undoManager.endUndoGrouping()
}
fileprivate func registerUndoRemoveLast(number: Digits) {
self.registerUndo()
undoManager.beginUndoGrouping()
undoManager.registerUndo(withTarget: self, selector: #selector(undo), object: nil)
if let last = number.last {
switch last {
case .decimal:
undoManager.setActionName("\(NSLocalizedString("Remove", comment: "")) \(last.value)")
return
default:
break
}
}
undoManager.setActionName("\(NSLocalizedString("Remove", comment: "")) \(number.formatted)")
undoManager.endUndoGrouping()
}
fileprivate func registerUndoRemoveLast(operation: Operator) {
self.registerUndo()
undoManager.beginUndoGrouping()
undoManager.registerUndo(withTarget: self, selector: #selector(undo), object: nil)
undoManager.setActionName("\(NSLocalizedString("Remove", comment: "")) \(operation.localizedName)")
undoManager.endUndoGrouping()
}
private func registerUndo(isUndoing: Bool = false) {
if !isUndoing {
self.isUndoing = false
}
guard !self.isUndoing || isUndoing else { return }
if self.previousStacks.last == nil || self.stack != self.previousStacks.last! {
self.previousStacks.append(self.stack)
self.currentUndoStackIndex = self.previousStacks.count
}
if self.previousStacks.count > self.undoManager.levelsOfUndo {
self.previousStacks.removeFirst()
self.currentUndoStackIndex = self.previousStacks.count
}
}
@objc fileprivate func undo() {
if !self.isUndoing {
self.registerUndo(isUndoing: true)
self.currentUndoStackIndex -= 1
}
self.isUndoing = true
self.currentUndoStackIndex -= 1
if self.currentUndoStackIndex < 0 {
self.currentUndoStackIndex = 0
}
guard !self.previousStacks.isEmpty else {
self.clear()
return
}
self.stack = self.previousStacks[self.currentUndoStackIndex]
}
fileprivate func handleDidRedoNotification(_ notification: Notification) {
self.currentUndoStackIndex += 1
if self.currentUndoStackIndex >= self.previousStacks.count {
self.currentUndoStackIndex = self.previousStacks.count - 1
}
guard !self.previousStacks.isEmpty else {
self.clear()
return
}
self.stack = self.previousStacks[self.currentUndoStackIndex]
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment