Created
February 26, 2018 20:31
-
-
Save BellAppLab/2f63d5911a6b2258a4fda877c00a7e93 to your computer and use it in GitHub Desktop.
Testing Apple's Undo Manager
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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