Skip to content

Commit

Permalink
Merge branch 'release/1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
Lusito committed Dec 5, 2017
2 parents 5197746 + 4d1c895 commit 3163e06
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 15 deletions.
71 changes: 61 additions & 10 deletions README.md
Expand Up @@ -15,32 +15,83 @@ An UndoManager for TypeScript (and JavaScript). The basic idea is based on the U
- You can mark a save-point, to enable/disable your save-button.
- Undo/Redo non-edit related changes, like focus change, without it being marked as needs-to-be-saved.
- No dependencies
- Automated [unit tests](https://travis-ci.org/Lusito/typed-undo) with 100% [code coverage](https://coveralls.io/github/Lusito/typed-undo)
- Automated [unit tests](https://travis-ci.org/Lusito/typed-undo) with 100% [code coverage](https://coveralls.io/github/Lusito/typed-undo)
- Liberal license: [zlib/libpng](https://github.com/Lusito/typed-undo/blob/master/LICENSE)
- [Fully documented](https://lusito.github.io/typed-undo/index.html) using TypeDoc

### Installation via NPM

```npm install typed-undo --save```

### Basics
### Example

```typescript
import { UndoManager, UndoableEdit } from "typed-undo";

class MyUndoableEdit extends UndoableEdit {
class UndoableValueChange extends UndoableEdit {
private readonly oldValue: string;
private newValue: string;

public undo(): void {
}

public redo(): void {
}
private readonly applyValue: (value: string) => void;

public constructor(oldValue: string, newValue: string, applyValue: (value: string) => void) {
super();
this.oldValue = oldValue;
this.newValue = newValue;
this.applyValue = applyValue;
}

public undo(): void {
this.applyValue(this.oldValue);
}

public redo(): void {
this.applyValue(this.newValue);
}

public isSignificant(): boolean {
return this.oldValue !== this.newValue;
}

public merge(edit: UndoableEdit): boolean {
if (edit instanceof UndoableValueChange) {
this.newValue = edit.newValue;
return true;
}
return false;
}
}

const manager = new UndoManager();
// fixme: examples
manager.setListener(()=> {
console.log((manager.canUndo() ? "enable" : "disable") + " the undo button");
console.log((manager.canRedo() ? "enable" : "disable") + " the redo button");
console.log((manager.isModified() ? "enable" : "disable") + " the save button");
});
const applyValue = (value:string)=> console.log(`Value changed to "${value}"`);
const example = "basic";
if(example === "basic") {
applyValue("Foo Bar"); // Value changed to "Foo Bar"
manager.add(new UndoableValueChange("Hello World", "Foo Bar", applyValue));
manager.undo(); // Value changed to "Hello World"
manager.redo(); // Value changed to "Foo Bar"
} else if(example === "merge") {
manager.add(new UndoableValueChange("Hello World", "Foo Bar", applyValue));
console.log(manager.canUndo()); // true
console.log(manager.isModified()); // true
manager.add(new UndoableValueChange("Foo Bar", "Hello World", applyValue));
console.log(manager.canUndo()); // false
console.log(manager.isModified()); // false
} else if(example === "modified") {
console.log(manager.isModified()); // false
manager.add(new UndoableValueChange("Hello World", "Foo Bar", applyValue));
console.log(manager.isModified()); // true
manager.setUnmodified();
console.log(manager.isModified()); // false
manager.undo();
console.log(manager.isModified()); // true
manager.redo();
console.log(manager.isModified()); // false
}
```

### Report isssues
Expand Down
3 changes: 2 additions & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "typed-undo",
"version": "0.8.0",
"version": "1.0.0",
"description": "A low memory UndoManager for TypeScript (and JavaScript)",
"keywords": [
"TypeScript",
Expand Down Expand Up @@ -37,6 +37,7 @@
".tsx"
],
"exclude": [
"docs/**/*",
"coverage/**/*",
"test/**/*",
"dist/test/**/*",
Expand Down
8 changes: 7 additions & 1 deletion src/UndoManager.ts
Expand Up @@ -83,6 +83,8 @@ export class UndoManager {
* Test if there is anything to be saved
*/
public isModified() {
if (this.unmodifiedPosition === -1)
return true;
if (this.position === this.unmodifiedPosition)
return false;
if (this.edits.length <= this.unmodifiedPosition)
Expand Down Expand Up @@ -132,7 +134,9 @@ export class UndoManager {
private applyLimit(limit: number) {
let diff = this.edits.length - limit
if (diff > 0) {
// fixme: correct unmodifiedPosition and add tests for it
this.position = Math.max(0, this.position - diff);
if(this.unmodifiedPosition !== -1)
this.unmodifiedPosition = Math.max(0, this.unmodifiedPosition - diff);
this.edits.splice(0, diff);
}
}
Expand Down Expand Up @@ -240,6 +244,8 @@ export class UndoManager {

this.applyLimit(this.limit);
this.position = this.edits.length;
if(this.unmodifiedPosition >= this.position)
this.unmodifiedPosition = -1;
if (this.listener)
this.listener();
}
Expand Down
136 changes: 133 additions & 3 deletions test/UndoManagerTests.ts
Expand Up @@ -32,6 +32,14 @@ class UndoableMergeSpy extends UndoableSpy {
}
}

class UndoableReplaceSpy extends UndoableSpy {
public replaceCalls = 0;
public replace(edit: UndoableEdit): boolean {
this.replaceCalls++;
return edit instanceof UndoableReplaceSpy;
}
}

class UndoableInsignificantSpy extends UndoableSpy {
public isSignificant(): boolean {
return false;
Expand Down Expand Up @@ -273,7 +281,58 @@ class UndoableMerge extends UndoableSpy {
assert.equal(undoable3.redoneCount, 1);
}

//Fixme: set limits after adding undoables
@test limit_after_add() {
const manager = new UndoManager();
const undoable1 = new UndoableSpy();
const undoable2 = new UndoableSpy();
const undoable3 = new UndoableSpy();
manager.add(undoable1);
manager.add(undoable2);
manager.add(undoable3);
manager.setLimit(2);
assert.equal(manager.getLimit(), 2);
assert.isTrue(manager.canUndo());
assert.isFalse(manager.canRedo());
manager.undo();
assert.equal(undoable1.undoneCount, 0);
assert.equal(undoable1.redoneCount, 0);
assert.equal(undoable2.undoneCount, 0);
assert.equal(undoable2.redoneCount, 0);
assert.equal(undoable3.undoneCount, 1);
assert.equal(undoable3.redoneCount, 0);
assert.isTrue(manager.canUndo());
assert.isTrue(manager.canRedo());
manager.undo();
assert.equal(undoable1.undoneCount, 0);
assert.equal(undoable1.redoneCount, 0);
assert.equal(undoable2.undoneCount, 1);
assert.equal(undoable2.redoneCount, 0);
assert.equal(undoable3.undoneCount, 1);
assert.equal(undoable3.redoneCount, 0);
assert.isFalse(manager.canUndo());
assert.isTrue(manager.canRedo());
}

@test limit_after_add_save_marker_gone() {
const manager = new UndoManager();
assert.isFalse(manager.isModified());
manager.add(new UndoableSpy());
manager.add(new UndoableSpy());
manager.add(new UndoableSpy());
manager.setUnmodified();
manager.undo();
manager.add(new UndoableSpy());
manager.setLimit(2);
assert.isTrue(manager.isModified());
while(manager.canUndo()) {
manager.undo();
assert.isTrue(manager.isModified());
}
while(manager.canRedo()) {
manager.redo();
assert.isTrue(manager.isModified());
}
}

@test errors() {
const manager = new UndoManager();
Expand Down Expand Up @@ -387,6 +446,27 @@ class UndoableMerge extends UndoableSpy {
assert.isFalse(manager.isModified());
}

@test save_marker_gone() {
const manager = new UndoManager();
assert.isFalse(manager.isModified());
manager.add(new UndoableSpy());
manager.add(new UndoableSpy());
manager.setUnmodified();
assert.isFalse(manager.isModified());
manager.undo();
assert.isTrue(manager.isModified());
manager.add(new UndoableSpy());
assert.isTrue(manager.isModified());
while(manager.canUndo()) {
manager.undo();
assert.isTrue(manager.isModified());
}
while(manager.canRedo()) {
manager.redo();
assert.isTrue(manager.isModified());
}
}

@test save_marker_insignificant() {
const manager = new UndoManager();
const undoable1 = new UndoableSpy();
Expand Down Expand Up @@ -438,6 +518,56 @@ class UndoableMerge extends UndoableSpy {
assert.isTrue(manager.isModified());
}

//Fixme: replace
//Fixme: listener
@test replace() {
const manager = new UndoManager();
const undoable1 = new UndoableReplaceSpy();
const undoable2 = new UndoableReplaceSpy();
manager.add(undoable1);
manager.add(undoable2);
assert.isTrue(manager.canUndo());
assert.isFalse(manager.canRedo());
manager.undo();
assert.equal(undoable1.undoneCount, 0);
assert.equal(undoable1.redoneCount, 0);
assert.equal(undoable2.undoneCount, 1);
assert.equal(undoable2.redoneCount, 0);
assert.isFalse(manager.canUndo());
assert.isTrue(manager.canRedo());
manager.redo();
assert.equal(undoable1.undoneCount, 0);
assert.equal(undoable1.redoneCount, 0);
assert.equal(undoable2.undoneCount, 1);
assert.equal(undoable2.redoneCount, 1);
assert.isTrue(manager.canUndo());
assert.isFalse(manager.canRedo());
}

@test listener() {
let count = 0;
const manager = new UndoManager();
manager.setListener(() => count++);
assert.equal(count, 0);
manager.add(new UndoableSpy());
assert.equal(count, 1);
manager.undo();
assert.equal(count, 2);
manager.redo();
assert.equal(count, 3);
manager.clear();
assert.equal(count, 4);

manager.add(new UndoableSpy());
assert.equal(count, 5);
manager.add(new UndoableInsignificantSpy());
assert.equal(count, 6);
manager.add(new UndoableInsignificantSpy());
assert.equal(count, 7);
manager.add(new UndoableSpy());
assert.equal(count, 8);
manager.undo();
assert.equal(count, 9);
manager.undo();
assert.equal(count, 10);
assert.isFalse(manager.canUndo());
}
}

0 comments on commit 3163e06

Please sign in to comment.