Skip to content

Instantly share code, notes, and snippets.

@LeandrodeLimaC
Created March 13, 2023 19:59
Show Gist options
  • Save LeandrodeLimaC/276f40c388c608ab9dedd3484d2768ba to your computer and use it in GitHub Desktop.
Save LeandrodeLimaC/276f40c388c608ab9dedd3484d2768ba to your computer and use it in GitHub Desktop.
undoRedo function
type History<T> = Partial<T>[]
const undoRedo = <T extends Record<string, unknown>>(object: T) => {
const history: History<T> = [{...object}]
let currIndex = 0
const getState = (key: any) => {
const state = history[currIndex][key]
return state
}
const setState = (key: any, value: unknown) => {
const state = {...history[currIndex]}
currIndex++
history.splice(currIndex, history.length - currIndex, {...state, [key]: value})
Object.assign(object, history[currIndex])
}
const deleteState = (key: any) => {
const state = {...history[currIndex]}
delete state[key]
currIndex++
history.splice(currIndex, history.length - currIndex, state)
delete object[key]
Object.assign(object, history[currIndex])
}
const undo = () => {
if(currIndex === 0)
throw new Error('No operation to undo')
currIndex--
Object.assign(object, history[currIndex])
Object.keys(object).forEach((key) => {
if(!history[currIndex][key]){
delete object[key]
}
})
}
const redo = () => {
if(currIndex + 1 === history.length)
throw new Error('No operation to redo')
currIndex++
Object.assign(object, history[currIndex])
Object.keys(object).forEach((key) => {
if(!history[currIndex][key]){
delete object[key]
}
})
}
return {
get: getState,
set: setState,
del: deleteState,
undo,
redo,
};
};
export {undoRedo};

Task

Create a function that returns a new basic key/value store. What is special about this store is that it will support undo and redo of its history.

This function takes an object and returns an object that has the following functions defined on it.

Methods set(key, value) Assigns the value to the key. If the key does not exist, creates it.

get(key) Returns the value associated to the key.

del(key) removes the key from the object.

undo() Undo the last operation (set or del) on the object. Throws an exception if there is no operation to undo.

redo() Redo the last undo operation (redo is only possible after an undo). Throws an exception if there is no operation to redo.

After set() or del() are called, there is nothing to redo.

All actions must affect to the object passed to undoRedo(object) function. So you can not work with a copy of the object.

Any set/del after an undo should disallow new undos.

import {assert, config} from "chai";
import {undoRedo} from "./solution";
config.truncateThreshold = 0;
describe("sample tests", () => {
it("should work on get/set tests", () => {
const obj = {
x: 1,
y: 2
};
const unRe = undoRedo(obj);
assert.strictEqual(unRe.get('x'), 1, 'The get method returns the value of a key');
unRe.set('x', 3);
assert.strictEqual(unRe.get('x'), 3, 'The set method change the value of a key');
});
it("should work on simple undo", () => {
const obj = {
x: 1,
y: 2
};
const unRe = undoRedo(obj);
unRe.set('y', 10);
assert.strictEqual(unRe.get('y'), 10, 'The get method returns the value of a key');
unRe.undo();
assert.strictEqual(unRe.get('y'), 2, 'The undo method restores the previous state');
assert.throws(() => unRe.undo());
assert.strictEqual(unRe.get('y'), 2);
});
it("should work on simple redo", () => {
const obj = {
x: 1,
y: 2
};
const unRe = undoRedo(obj);
unRe.set('y', 10);
assert.strictEqual(unRe.get('y'), 10, 'The get method returns the value of a key');
unRe.undo();
assert.strictEqual(unRe.get('y'), 2, 'The undo method restores the previous state');
unRe.redo();
assert.strictEqual(unRe.get('y'), 10, 'The undo method restores the previous state');
assert.throws(() => unRe.redo());
assert.strictEqual(unRe.get('y'), 10);
});
it("should work on undo/redo", () => {
const obj = {
x: 1,
y: 2
};
const unRe = undoRedo(obj);
unRe.set('y', 10);
unRe.set('y', 100);
unRe.set('x', 150);
unRe.set('x', 50);
assert.strictEqual(unRe.get('y'), 100, 'The get method returns the value of a key');
assert.strictEqual(unRe.get('x'), 50, 'The get method returns the value of a key');
unRe.undo();
assert.strictEqual(unRe.get('x'), 150, 'The undo method restores the previous state');
assert.strictEqual(unRe.get('y'), 100, 'The y key stays the same');
unRe.redo();
assert.strictEqual(unRe.get('x'), 50, 'Undo the x value');
assert.strictEqual(unRe.get('y'), 100, 'The y key stays the same');
unRe.undo();
unRe.undo();
assert.strictEqual(unRe.get('x'), 1, 'Undo the x value');
assert.strictEqual(unRe.get('y'), 100, 'The y key stays the same');
unRe.undo();
unRe.undo();
assert.strictEqual(unRe.get('y'), 2, 'Undo the y value');
assert.strictEqual(unRe.get('x'), 1, 'The x key stays the same');
assert.throws(() => unRe.undo());
assert.strictEqual(unRe.get('y'), 2, 'There is nothing to undo');
unRe.redo();
unRe.redo();
unRe.redo();
unRe.redo();
assert.strictEqual(unRe.get('y'), 100, 'y key redo state');
assert.strictEqual(unRe.get('x'), 50, 'y key redo state');
assert.throws(() => unRe.redo());
assert.strictEqual(unRe.get('y'), 100, 'There is nothing to redo');
});
it('should work on a new key', () => {
const obj = {
x: 1,
y: 2
};
const unRe = undoRedo(obj);
unRe.set('z', 10);
assert.strictEqual(unRe.get('z'), 10, 'A new key has been added');
unRe.undo();
assert.strictEqual(unRe.get('z'), undefined, 'The z key should not exist');
unRe.redo();
assert.strictEqual(unRe.get('z'), 10, 'A new key has been added');
});
it('should delete a key', () => {
const obj = {
x: 1,
y: 2
};
const unRe = undoRedo(obj);
unRe.del('x');
assert.strictEqual(unRe.get('x'), undefined, 'The x key should not exist');
assert.isFalse(obj.hasOwnProperty('x'), 'The x key should be deleted');
unRe.undo();
assert.strictEqual(unRe.get('x'), 1, 'A new key has been added');
unRe.redo();
assert.strictEqual(unRe.get('x'), undefined, 'The x key should not exist');
console.log("teste obj", obj)
assert.isFalse(obj.hasOwnProperty('x'), 'The x key should be deleted');
});
it('should work on all mixed tests', () => {
const obj = {};
const unRe = undoRedo(obj);
assert.deepEqual(obj, {}, 'The obj object has no keys');
unRe.set('x', 5);
assert.strictEqual(unRe.get('x'), 5, 'x key has been added');
unRe.set('y', 10);
assert.strictEqual(unRe.get('y'), 10, 'y key has been added');
unRe.set('y', 8);
assert.strictEqual(unRe.get('y'), 8, 'y key has change its value');
unRe.del('y');
assert.strictEqual(unRe.get('y'), undefined, 'y key should not exist');
unRe.undo();
assert.strictEqual(unRe.get('y'), 8, 'y key exists');
unRe.undo();
assert.strictEqual(unRe.get('y'), 10, 'y key has 10 value');
unRe.undo();
assert.strictEqual(unRe.get('y'), undefined, 'y key should not exist');
unRe.undo();
assert.strictEqual(unRe.get('x'), undefined, 'x key should not exist');
unRe.redo();
unRe.redo();
unRe.redo();
assert.deepEqual(obj, {x: 5, y: 8}, 'Redo all actions');
unRe.redo();
unRe.set('x', 55);
assert.deepEqual(obj, {x: 55}, 'Redo three actions');
unRe.undo();
assert.deepEqual(obj, {x: 5}, 'Redo one actions');
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment