Skip to content

Commit

Permalink
test: update compare method. add checkDeepValue
Browse files Browse the repository at this point in the history
  • Loading branch information
Jenesius committed May 22, 2023
1 parent 3ab4a5c commit 0e03c5f
Show file tree
Hide file tree
Showing 11 changed files with 374 additions and 47 deletions.
7 changes: 6 additions & 1 deletion src/classes/CompareEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ export default class CompareEvent {
* */
static restoreByName(compareEvent: CompareEvent, name: string) {
return new CompareEvent(
compareEvent.comparison.filter(comp => comp.name.startsWith(name))
compareEvent.comparison
.filter(comp => comp.name.startsWith(name))
.map(comp => {
comp.name = comp.name.slice(name.length + 1);
return comp;
}) // Удаляем приставку + 1(символ '.')
)
}
}
1 change: 1 addition & 0 deletions src/classes/Form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export default class Form extends EventEmitter implements FormDependence {
[this.name as string]: data
});
}

const grandData = grandObject(data);
this.mergeValues(grandData);
this.notify('value', grandData);
Expand Down
5 changes: 5 additions & 0 deletions src/utils/check-deep-value.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import checkPrimitiveValue from "./check-primitive-value";

export default function checkDeepValue(value: unknown) {
return !checkPrimitiveValue(value) && !Array.isArray(value) && !Object.isFrozen(value);
}
3 changes: 3 additions & 0 deletions src/utils/check-primitive-type.ts
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
/**
* @deprecated
* */
export default (v: any) => v === null || v === undefined || Array.isArray(v) || typeof v !== 'object';
80 changes: 60 additions & 20 deletions src/utils/compare-changes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import checkPrimitiveValue from "./check-primitive-value";
import concatName from "./concat-name";
import mergeObjects from "./merge-objects";
import copyObject from "./copy-object";
import checkDeepValue from "./check-deep-value";

/**
* @description Вернёт массив: результат сравнения. Результат сравнения из себя представляет название поля, которые
Expand All @@ -15,42 +18,79 @@ import concatName from "./concat-name";
*
* @param {Object} newValue объект новых(исходных) значений
* @param {Object} oldValue объект старых значений
*
* COMPARE OBJECT
*/
export default function compareChanges(newValue: unknown, oldValue: unknown): CompareItem[] {
return compare(newValue, oldValue)
}
function compare(newValue: any, oldValue: any, name: string = ''): CompareItem[] {
function step(newValue: any, oldValue: any, name: string) {
if (checkPrimitiveValue(newValue) && checkPrimitiveValue(oldValue)) {
if (newValue !== oldValue)
array.push({ name, newValue, oldValue })
}
else {
const changes = compare(newValue, oldValue, name);
if (changes.length) {
array.push({
name: name,
newValue: newValue,
oldValue: oldValue
})
array.push(...changes);
}
function step(array: CompareItem[], newValue: any, oldValue: any, name: string) {
if (!checkDeepValue(newValue) && !checkDeepValue(oldValue)) {
if (newValue !== oldValue)
array.push({ name, newValue, oldValue })
}
else {
const changes = compare(newValue, oldValue, name);
if (changes.length) {
array.push({
name: name,
newValue: newValue,
oldValue: oldValue
})
array.push(...changes);
}
}
}

function compare(newValue: any, oldValue: any, name: string = ''): CompareItem[] {

const array: CompareItem[] = [];

if (!checkPrimitiveValue(newValue))
if (checkDeepValue(newValue))
for(let key in newValue as any)
step(newValue?.[key], oldValue?.[key], concatName(name, key));
step(array, newValue?.[key], oldValue?.[key], concatName(name, key));

// Проходим по всем полям oldValue
// Т.к. часть полей мы уже отбросили в for...in для newValue, мы проверяем только те свойства, которых нет в новом
// объекте. Именно по этому первым параметром в step передаётся undefined
if (!checkPrimitiveValue(oldValue))
if (checkDeepValue(oldValue))
for(let key in oldValue as any)
if (!newValue?.hasOwnProperty(key))
step(undefined, oldValue?.[key], concatName(name, key));
step(array, undefined, oldValue?.[key], concatName(name, key));

return array;
}

/**
*
* COMPARE CHANGES
* @description В отличии от предыдущей функции, данная функция принимает изменения и объект, на который будут
* производиться изменения. Из этого можно сделать вывод, что второй объект необходим лишь для двух вещей:
* 1. Было ли поле изменено. В случае, если в изменениях пришло {name: "J"}, а в объекте и так было поле {name: "J"}, то
* данное поле не будет помечено, как изменённое.
* 2. Чтобы получить старое значение. (oldValue)
* То есть мы уже имеем набор изменений, на полноценное сравнивать два объекта не надо, а лишь надо спроецировать первый
* (изменения) на второй и сравнить, какие именно изменения будут произведены.
*
* Также нужно помнить, что изменения лишь проецируются на исходные значения. Иными словами, если
* исходные значения {coordinate: {x: 1}}
* изменения: {coordinate: {y: 2}}
* результирующий объект(который предполагается): { coordinate: { x: 1, y: 2 } }
* и в данном случае мы получаем, что у нас два изменения:
* [
* { name: 'coordinate', newValue: { x: 1, y: 2 }, oldValue: { x: 1, y: 2 } },
* { name: 'coordinate.y', newValue: 2, oldValue: undefined }
* ]
* */
export function compareMergeChanges(sourceValue: any, changes: any, name = '') {
const newObject = mergeObjects(copyObject(sourceValue), changes);
return compareChanges(newObject, sourceValue);

const array: CompareItem[] = [];

if (!checkPrimitiveValue(changes))
for(let key in changes as any)
step(array, changes?.[key], sourceValue?.[key], concatName(name, key));

return array;
}
Expand Down
2 changes: 1 addition & 1 deletion src/utils/copy-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ export default function copyObject<T>(object: T): T {
const outputObject:any = {};

if (checkPrimitiveType(object)) return object;

Object.entries(object).forEach(([key, value]) => {

outputObject[key] = copyObject(value);
})

Expand Down
14 changes: 2 additions & 12 deletions src/utils/is-end-point-value.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,9 @@
import checkPrimitiveType from "./check-primitive-type";
import checkDeepValue from "./check-deep-value";

/**
* @description Method using for check for value is endpoint?
* @return {Boolean} If provided object is primitive or frozen return true, otherwise return false.
*/
export default function isEndPointValue(v: any) {

if (checkPrimitiveType(v)) return true;

/**
* If value of frozen, For example, in case passing File or some Class Data.
*/
if (
Object.isFrozen(v)
) return true;

return false;
return !checkDeepValue(v);
}
23 changes: 23 additions & 0 deletions tests/utils/check-deep-value.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import checkDeepValue from "./../../src/utils/check-deep-value";

describe("Check deep value", () => {
test("Array is not deep value", () => {
expect(checkDeepValue([1,2])).toBe(false)
})
test("Frozen object is not deep value", () => {
const data = Object.freeze({a: 1})
expect(checkDeepValue(data)).toBe(false)
})
test("Primitive is not deep value", () => {
expect(checkDeepValue(1)).toBe(false)
expect(checkDeepValue("1")).toBe(false)
expect(checkDeepValue(null)).toBe(false)
expect(checkDeepValue(undefined)).toBe(false)
expect(checkDeepValue(true)).toBe(false)
})
test("Object is deep value", () => {
expect(checkDeepValue({
name: "Jenesius"
})).toBe(true)
})
})
75 changes: 62 additions & 13 deletions tests/utils/compare-changes.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import compareChanges from "./../../src/utils/compare-changes";

describe("Testing compare changes", () => {

test("Old value is empty object. It should return all props from new values.",() => {
const newValue = {
name: "Jenesius",
Expand Down Expand Up @@ -83,18 +82,68 @@ describe("Testing compare changes", () => {
{ name: "obj_1.obj_2.obj_3.obj_4.obj_5.obj_6", newValue: newValue.obj_1.obj_2.obj_3.obj_4.obj_5.obj_6, oldValue: undefined},
{ name: "obj_1.obj_2.obj_3.obj_4.obj_5.obj_6.obj_7", newValue: newValue.obj_1.obj_2.obj_3.obj_4.obj_5.obj_6.obj_7, oldValue: undefined},
])

})
test("Replacement value of composite object.",() => {
const newObject = {
address: {
city: "Mogilev",
}
};
const oldObject = {
address: {
country: "Belarus",
city: "Unknown"
}
}
expect(compareChanges(newObject, oldObject)).toEqual([
{
name: "address", newValue: {city: "Mogilev"}, oldValue: { country: "Belarus", city: "Unknown" }
},
{
name: "address.city", newValue: "Mogilev", oldValue: "Unknown"
},
{
name: "address.country", newValue: undefined, oldValue: "Belarus"
}
])

test("",() => {})
test("",() => {})
test("",() => {})
test("",() => {})
test("",() => {})
test("",() => {})
test("",() => {})
test("",() => {})
test("",() => {})
test("",() => {})

})
test("Replacement composite object to null",() => {
const newValue = {
address: null
}
const oldValue = {
address: {
city: "Mogilev",
country: "Belarus"
}
}
expect(compareChanges(newValue, oldValue)).toEqual([
{ name: "address", newValue: null, oldValue: { city: "Mogilev", country: "Belarus" } },
{ name: "address.city", newValue: undefined, oldValue: "Mogilev" },
{ name: "address.country", newValue: undefined, oldValue: "Belarus" },
])
})
test("New value is null",() => {
const newValue = null;
const oldValue = {
name: "Jenesius"
}
expect(compareChanges(newValue, oldValue)).toEqual([
{
name: "name", newValue: undefined, oldValue: "Jenesius"
}
])
})
test("Old value is null",() => {
const newValue = {
age: 24
};
const oldValue = null
expect(compareChanges(newValue, oldValue)).toEqual([
{
name: "age", newValue: 24, oldValue: undefined
}
])
})
})
37 changes: 37 additions & 0 deletions tests/utils/compare-event.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import CompareEvent from "./../../src/classes/CompareEvent";

describe("Compare Event", () => {
test("Should restore fields that bind with address", () => {
const newValue = {
address: {
city: "Mogilev"
},
name: "Jenesius",
age: 24
}
const event = new CompareEvent(newValue, {});
const addressEvent = CompareEvent.restoreByName(event, 'address');

expect(addressEvent.comparison).toEqual([
{
name: '', newValue: { city: "Mogilev" }, oldValue: undefined
},
{
name: "city", newValue: "Mogilev", oldValue: undefined
}
])
})
test("Should restore fields that bind with address", () => {
const newValue = {
address: {
city: "Mogilev"
},
name: "Jenesius",
age: 24
}
const event = new CompareEvent(newValue, {});
const addressEvent = CompareEvent.restoreByName(event, 'coordinate');

expect(addressEvent.comparison).toEqual([])
})
})
Loading

0 comments on commit 0e03c5f

Please sign in to comment.