Skip to content

Instantly share code, notes, and snippets.

@ArturoRodriguezRomero
Last active July 9, 2019 13:10
Show Gist options
  • Select an option

  • Save ArturoRodriguezRomero/356b1f0032f7a9457ec4f3055e66d9e1 to your computer and use it in GitHub Desktop.

Select an option

Save ArturoRodriguezRomero/356b1f0032f7a9457ec4f3055e66d9e1 to your computer and use it in GitHub Desktop.
Typescript class that keeps a history of previous values. Allows for GET, SET, UNDO, REDO, RESET, RESTORE.
/**
* Class that keeps history of a value ~ past values.
*/
export class Snapshot<T> {
private readonly initial: T;
private readonly value: T;
private readonly history: Snapshot<T>[];
private readonly current: number;
private constructor(
value: T,
initial: T,
history: Snapshot<T>[] = [],
current: number = 0
) {
this.initial = initial;
this.value = value;
this.history = history;
this.current = current;
}
/**
* Returns a new Snapshot object.
* @param value Initial value. History first item.
* @returns new Snapshot object with initial value.
* @typeparam T type of value.
*/
public static create<T>(value: T): Snapshot<T> {
return new Snapshot(value, value, [new Snapshot<T>(value, value)]);
}
/**
* Adds item to history and returns a new Snapshot object.
* @param value New value. History last item.
* @returns new Snapshot object with value as current.
*/
public set(value: T): Snapshot<T> {
const current = this.current + 1;
const history = [
...this.history.slice(0, current),
new Snapshot<T>(value, this.initial)
];
return new Snapshot<T>(value, this.initial, history, current);
}
/**
* Returns the current value.
* @returns current value.
*/
public get(): T {
return this.value;
}
/**
* Returns all values.
* @returns all values that this Snapshot object ever held.
*/
public historic(): T[] {
return this.history.map(
(value: Snapshot<T>): T => {
return value.get();
}
);
}
/**
* Sets current value to previous in history and returns a new Snapshot object.
* @returns new Snapshot object with current as previous value.
*/
public undo(): Snapshot<T> {
const previous = this.current - 1;
return this.setCurrent(previous);
}
/**
* Sets current value to next in history and returns a new Snapshot object.
* @returns new Snapshot object with current as next value.
*/
public redo(): Snapshot<T> {
const next = this.current + 1;
return this.setCurrent(next);
}
/**
* Sets current value to initial in history and returns a new Snapshot object.
* @returns new Snapshot object with initial as current
*/
public reset(): Snapshot<T> {
const next = this.current + 1;
return new Snapshot(this.initial, this.initial, this.history, next);
}
/**
* Returns a new Snapshot object with a cleared history.
* @returns new Snapshot object with cleared history.
*/
public restore(): Snapshot<T> {
return Snapshot.create(this.initial);
}
private setCurrent(to: number): Snapshot<T> {
const existsBefore = to !== -1;
const existsAfter = to !== this.history.length;
const exists = existsBefore && existsAfter;
if (!exists) {
return new Snapshot<T>(this.value, this.initial, this.history);
}
const value = this.history[to].value;
return new Snapshot<T>(value, this.initial, this.history, to);
}
}
import { Snapshot } from "./Snapshot";
describe("Snapshot", () => {
let value: Snapshot<number>;
beforeEach(() => {
value = Snapshot.create<number>(0);
});
describe("get", () => {
it("should return initial value", () => {
const given = value.get();
const expected = 0;
expect(given).toBe(expected);
});
});
describe("set", () => {
it("should set new value", () => {
const given = value.set(1).get();
const expected = 1;
expect(given).toBe(expected);
});
it("should set a new new value", () => {
const given = value
.set(2)
.set(3)
.get();
const expected = 3;
expect(given).toBe(expected);
});
});
describe("undo", () => {
it("should set current to previous value", () => {
const given = value
.set(2)
.set(3)
.undo()
.get();
const expected = 2;
expect(given).toBe(expected);
});
it("should return initial value", () => {
const given = value.undo().get();
const expected = 0;
expect(given).toBe(expected);
});
});
describe("redo", () => {
it("should return initial value", () => {
const given = value.redo().get();
const expected = 0;
expect(given).toBe(expected);
});
it("should redo to next value", () => {
const given = value
.set(2)
.set(3)
.undo()
.redo()
.get();
const expected = 3;
expect(given).toBe(expected);
});
it("should return initial value", () => {
const given = value
.set(2)
.set(3)
.undo()
.redo()
.get();
const expected = 3;
expect(given).toBe(expected);
});
it("should redo multiple to next value", () => {
const given = value
.set(2)
.set(3)
.undo()
.undo()
.redo()
.redo()
.get();
const expected = 3;
expect(given).toBe(expected);
});
});
describe("reset", () => {
it("should set next value to initial", () => {
const given = value
.set(2)
.set(3)
.set(4)
.reset()
.get();
const expected = 0;
expect(given).toBe(expected);
});
});
describe("historic", () => {
it("should return historic array", () => {
const given = value
.set(2)
.set(3)
.historic();
const expected = [0, 2, 3];
expect(given).toEqual(expected);
});
});
describe("restore", () => {
it("should restore to initial and erase history", () => {
const given = value
.set(2)
.set(3)
.restore();
const expected = 0;
expect(given.get()).toEqual(expected);
expect(given.historic().length).toBe(1);
});
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment