Skip to content

Commit

Permalink
change the API for generating test operations
Browse files Browse the repository at this point in the history
was: extra boolean argument on "observe" and extra object argument on "generate" and "compare"
is: extra boolean argument "generate" and "compare"

also this commit adds way more tests for test operation generation
  • Loading branch information
warpech committed Jul 2, 2019
1 parent f078a4b commit 1498c09
Show file tree
Hide file tree
Showing 4 changed files with 279 additions and 65 deletions.
24 changes: 12 additions & 12 deletions README.md
Expand Up @@ -154,15 +154,15 @@ var patch = jsonpatch.generate(observer);
// ];
```

Generating patches with test operations:
Generating patches with test operations for values in the first object:

```js
var document = { firstName: "Joachim", lastName: "Wester", contactDetails: { phoneNumbers: [ { number:"555-123" }] } };
var observer = jsonpatch.observe(document, undefined, true);
var observer = jsonpatch.observe(document);
document.firstName = "Albert";
document.contactDetails.phoneNumbers[0].number = "123";
document.contactDetails.phoneNumbers.push({ number:"456" });
var patch = jsonpatch.generate(observer);
var patch = jsonpatch.generate(observer, true);
// patch == [
// { op: "test", path: "/firstName", value: "Joachim"},
// { op: "replace", path: "/firstName", value: "Albert"},
Expand All @@ -181,12 +181,12 @@ var diff = jsonpatch.compare(documentA, documentB);
//diff == [{op: "replace", path: "/user/lastName", value: "Collins"}]
```

Comparing two object trees with test operations:
Comparing two object trees with test operations for values in the first object:

```js
var documentA = {user: {firstName: "Albert", lastName: "Einstein"}};
var documentB = {user: {firstName: "Albert", lastName: "Collins"}};
var diff = jsonpatch.compare(documentA, documentB, {inversible: true});
var diff = jsonpatch.compare(documentA, documentB, true);
//diff == [
// {op: "test", path: "/user/lastName", value: "Einstein"},
// {op: "replace", path: "/user/lastName", value: "Collins"}
Expand Down Expand Up @@ -292,17 +292,17 @@ Retrieves a value from a JSON document by a JSON pointer.

Returns the value.

#### `jsonpatch.observe(document: any, callback?: Function, inversible: boolean = false): Observer`
#### `jsonpatch.observe(document: any, callback?: Function): Observer`

Sets up an deep observer on `document` that listens for changes in object tree. When changes are detected, the optional
callback is called with the generated patches array as the parameter. If inversible is true, then observer will generate test operations.
callback is called with the generated patches array as the parameter.

Returns `observer`.

#### `jsonpatch.generate(document: any, observer: Observer, opts?: { inversible: boolean }): Operation[]`
#### `jsonpatch.generate(document: any, observer: Observer, invertible = false): Operation[]`

If there are pending changes in `obj`, returns them synchronously. If a `callback` was defined in `observe`
method, it will be triggered synchronously as well. If opts.inversible is undefined then fallback to observer.inversible.
method, it will be triggered synchronously as well. If `invertible` is true, then each change will be preceded by a test operation of the value before the change.

If there are no pending changes in `obj`, returns an empty array (length 0).

Expand All @@ -312,9 +312,9 @@ Destroys the observer set up on `document`.

Any remaining changes are delivered synchronously (as in `jsonpatch.generate`). Note: this is different that ES6/7 `Object.unobserve`, which delivers remaining changes asynchronously.

#### `jsonpatch.compare(document1: any, document2: any, opts?: { inversible: boolean }): Operation[]`
#### `jsonpatch.compare(document1: any, document2: any, invertible = false): Operation[]`

Compares object trees `document1` and `document2` and returns the difference relative to `document1` as a patches array. If opts.inversible is true, test operations will be generated.
Compares object trees `document1` and `document2` and returns the difference relative to `document1` as a patches array. If `invertible` is true, then each change will be preceded by a test operation of the value in `document1`.

If there are no differences, returns an empty array (length 0).

Expand Down Expand Up @@ -376,7 +376,7 @@ Functions `applyPatch`, `applyOperation`, and `validate` accept a `validate`/ `v
If you pass a validator, it will be called with four parameters for each operation, `function(operation, index, tree, existingPath)` and it is expected to throw `JsonPatchError` when your conditions are not met.

- `operation` The operation it self.
- `index` `operation`'s index in the patch array (if application).
- `index` `operation`'s index in the patch array (if application).
- `tree` The object that is supposed to be patched.
- `existingPath` the path `operation` points to.

Expand Down
25 changes: 11 additions & 14 deletions src/duplex.ts
Expand Up @@ -17,7 +17,6 @@ export interface Observer<T> {
patches: Operation[];
unobserve: () => void;
callback: (patches: Operation[]) => void;
inversible: boolean;
}

var beforeDict = new WeakMap();
Expand Down Expand Up @@ -64,7 +63,7 @@ export function unobserve<T>(root: T, observer: Observer<T>) {
/**
* Observes changes made to an object, which can then be retrieved using generate
*/
export function observe<T>(obj: Object|Array<T>, callback?: (patches: Operation[]) => void, inversible: boolean = false): Observer<T> {
export function observe<T>(obj: Object|Array<T>, callback?: (patches: Operation[]) => void): Observer<T> {
var patches = [];
var observer;
var mirror = getMirror(obj);
Expand All @@ -81,7 +80,7 @@ export function observe<T>(obj: Object|Array<T>, callback?: (patches: Operation[
return observer;
}

observer = { inversible };
observer = {};

mirror.value = _deepClone(obj);

Expand Down Expand Up @@ -145,11 +144,10 @@ export function observe<T>(obj: Object|Array<T>, callback?: (patches: Operation[
/**
* Generate an array of patches from an observer
*/
export function generate<T>(observer: Observer<Object>, opts: { inversible?: boolean } = {}): Operation[] {
export function generate<T>(observer: Observer<Object>, invertible = false): Operation[] {
var mirror = beforeDict.get(observer.object);
var inversible = typeof opts.inversible !== "undefined" ? opts.inversible : observer.inversible;

_generate(mirror.value, observer.object, observer.patches, "", { inversible });
_generate(mirror.value, observer.object, observer.patches, "", invertible);
if (observer.patches.length) {
applyPatch(mirror.value, observer.patches);
}
Expand All @@ -164,7 +162,7 @@ export function generate<T>(observer: Observer<Object>, opts: { inversible?: boo
}

// Dirty check if obj is different from mirror, generate patches and update mirror
function _generate(mirror, obj, patches, path, opts = { inversible: false }) {
function _generate(mirror, obj, patches, path, invertible) {
if (obj === mirror) {
return;
}
Expand All @@ -177,7 +175,6 @@ function _generate(mirror, obj, patches, path, opts = { inversible: false }) {
var oldKeys = _objectKeys(mirror);
var changed = false;
var deleted = false;
var { inversible } = opts;

//if ever "move" operation is implemented here, make sure this test runs OK: "should not generate the same patch twice (move)"

Expand All @@ -189,22 +186,22 @@ function _generate(mirror, obj, patches, path, opts = { inversible: false }) {
var newVal = obj[key];

if (typeof oldVal == "object" && oldVal != null && typeof newVal == "object" && newVal != null) {
_generate(oldVal, newVal, patches, path + "/" + escapePathComponent(key), opts);
_generate(oldVal, newVal, patches, path + "/" + escapePathComponent(key), invertible);
}
else {
if (oldVal !== newVal) {
changed = true;
if (inversible) patches.push({ op: "test", path: path + "/" + escapePathComponent(key), value: _deepClone(oldVal) });
if (invertible) patches.push({ op: "test", path: path + "/" + escapePathComponent(key), value: _deepClone(oldVal) });
patches.push({ op: "replace", path: path + "/" + escapePathComponent(key), value: _deepClone(newVal) });
}
}
}
else if(Array.isArray(mirror) === Array.isArray(obj)) {
if (inversible) patches.push({ op: "test", path: path + "/" + escapePathComponent(key), value: _deepClone(oldVal) })
if (invertible) patches.push({ op: "test", path: path + "/" + escapePathComponent(key), value: _deepClone(oldVal) })
patches.push({ op: "remove", path: path + "/" + escapePathComponent(key) });
deleted = true; // property has been deleted
} else {
if (inversible) patches.push({ op: "test", path, value: mirror });
if (invertible) patches.push({ op: "test", path, value: mirror });
patches.push({ op: "replace", path, value: obj });
changed = true;
}
Expand All @@ -224,8 +221,8 @@ function _generate(mirror, obj, patches, path, opts = { inversible: false }) {
/**
* Create an array of patches from the differences in two objects
*/
export function compare(tree1: Object | Array<any>, tree2: Object | Array<any>, opts?: { inversible: boolean }): Operation[] {
export function compare(tree1: Object | Array<any>, tree2: Object | Array<any>, invertible = false): Operation[] {
var patches = [];
_generate(tree1, tree2, patches, '', opts);
_generate(tree1, tree2, patches, '', invertible);
return patches;
}
20 changes: 10 additions & 10 deletions test/spec/duplexBenchmark.js
Expand Up @@ -146,7 +146,7 @@ suite.add('compare operation same but deep objects', {
});

// Benchmark generating test operations
suite.add('generate operation, with inversible set to true', {
suite.add('generate operation, invertible = true', {
setup: function() {
var obj = {
firstName: 'Albert',
Expand All @@ -160,18 +160,18 @@ suite.add('generate operation, with inversible set to true', {
}
]
};
var observer = jsonpatch.observe(obj, undefined, true);
var observer = jsonpatch.observe(obj);
},
fn: function() {
obj.firstName = 'Joachim';
obj.lastName = 'Wester';
obj.phoneNumbers[0].number = '123';
obj.phoneNumbers[1].number = '456';

var patches = jsonpatch.generate(observer);
var patches = jsonpatch.generate(observer, true);
}
});
suite.add('generate operation and re-apply, with inversible set to true', {
suite.add('generate operation and re-apply, invertible = true', {
setup: function() {
var obj = {
firstName: 'Albert',
Expand All @@ -185,15 +185,15 @@ suite.add('generate operation and re-apply, with inversible set to true', {
}
]
};
var observer = jsonpatch.observe(obj, undefined, true);
var observer = jsonpatch.observe(obj);
},
fn: function() {
obj.firstName = 'Joachim';
obj.lastName = 'Wester';
obj.phoneNumbers[0].number = '123';
obj.phoneNumbers[1].number = '456';

var patches = jsonpatch.generate(observer);
var patches = jsonpatch.generate(observer, true);
obj2 = {
firstName: 'Albert',
lastName: 'Einstein',
Expand All @@ -210,7 +210,7 @@ suite.add('generate operation and re-apply, with inversible set to true', {
jsonpatch.applyPatch(obj2, patches);
}
});
suite.add('compare operation, with inversible set to true', {
suite.add('compare operation, invertible = true', {
setup: function() {
var obj = {
firstName: 'Albert',
Expand Down Expand Up @@ -238,11 +238,11 @@ suite.add('compare operation, with inversible set to true', {
};
},
fn: function() {
var patches = jsonpatch.compare(obj, obj2, { inversible: true });
var patches = jsonpatch.compare(obj, obj2, true);
}
});

suite.add('compare operation same but deep objects, with inversible set to true', {
suite.add('compare operation same but deep objects, invertible = true', {
setup: function() {
var depth = 10;

Expand Down Expand Up @@ -271,7 +271,7 @@ suite.add('compare operation same but deep objects, with inversible set to true'
var obj2 = obj;
},
fn: function() {
var patches = jsonpatch.compare(obj, obj2, { inversible: true });
var patches = jsonpatch.compare(obj, obj2, true);
}
});

Expand Down

0 comments on commit 1498c09

Please sign in to comment.