Browse Source

feat: add tests

pull/10/head
DoneDeal0 2 years ago
parent
commit
1606a01687
  1. 86
      dist/index.d.ts
  2. 239
      dist/index.js
  3. 231
      dist/index.mjs
  4. 12
      package.json
  5. 39
      src/list-diff.ts
  6. 10
      src/model.ts
  7. 43
      src/object-diff.ts
  8. 56
      test/list-diff.test.ts
  9. 288
      test/object-diff.test.ts
  10. 1056
      yarn.lock

86
dist/index.d.ts vendored

@ -1,51 +1,69 @@ @@ -1,51 +1,69 @@
type DiffStatus = "added" | "equal" | "moved" | "deleted" | "updated";
declare const GRANULARITY: Record<string, "basic" | "deep">;
type ListDiffStatus = "added" | "equal" | "moved" | "deleted" | "updated";
type ObjectDiffStatus = "added" | "equal" | "deleted" | "updated";
type ObjectData = Record<string, any> | undefined | null;
type ListData = any;
type Options = {
ignoreArrayOrder?: boolean;
type ObjectStatusTuple = readonly [
"added",
"equal",
"deleted",
"updated"
];
type ListStatusTuple = readonly [
"added",
"equal",
"deleted",
"moved",
"updated"
];
type isEqualOptions = {
ignoreArrayOrder?: boolean;
};
type ObjectOptions = {
ignoreArrayOrder?: boolean;
showOnly?: {
statuses: Array<ObjectStatusTuple[number]>;
granularity?: typeof GRANULARITY[keyof typeof GRANULARITY];
};
};
type ListOptions = {
showOnly?: Array<ListStatusTuple[number]>;
};
type ListDiff = {
type: "list";
status: DiffStatus;
diff: {
value: ListData;
prevIndex: number | null;
newIndex: number | null;
indexDiff: number | null;
status: DiffStatus;
}[];
type: "list";
status: ListDiffStatus;
diff: {
value: ListData;
prevIndex: number | null;
newIndex: number | null;
indexDiff: number | null;
status: ListDiffStatus;
}[];
};
type SubProperties = {
name: string;
previousValue: any;
currentValue: any;
status: DiffStatus;
subDiff?: SubProperties[];
};
type ObjectDiff = {
type: "object";
status: DiffStatus;
diff: {
property: string;
previousValue: any;
currentValue: any;
status: DiffStatus;
status: ObjectDiffStatus;
subPropertiesDiff?: SubProperties[];
}[];
};
type ObjectDiff = {
type: "object";
status: ObjectDiffStatus;
diff: {
property: string;
previousValue: any;
currentValue: any;
status: ObjectDiffStatus;
subPropertiesDiff?: SubProperties[];
}[];
};
declare function getObjectDiff(
prevData: ObjectData,
nextData: ObjectData,
options?: Options
): ObjectDiff;
declare function getObjectDiff(prevData: ObjectData, nextData: ObjectData, options?: ObjectOptions): ObjectDiff;
declare const getListDiff: (
prevList: ListData[] | undefined | null,
nextList: ListData[] | undefined | null
) => ListDiff;
declare const getListDiff: (prevList: ListData[] | undefined | null, nextList: ListData[] | undefined | null, options?: ListOptions) => ListDiff;
declare function isEqual(a: any, b: any, options?: Options): boolean;
declare function isEqual(a: any, b: any, options?: isEqualOptions): boolean;
declare function isObject(value: any): value is Record<string, any>;
export { getListDiff, getObjectDiff, isEqual, isObject };

239
dist/index.js vendored

@ -1,235 +1,8 @@ @@ -1,235 +1,8 @@
"use strict";
'use strict';
var r = {
ADDED: "added",
EQUAL: "equal",
MOVED: "moved",
DELETED: "deleted",
UPDATED: "updated",
};
function d(e, t, n = { ignoreArrayOrder: !1 }) {
return typeof e != typeof t
? !1
: Array.isArray(e)
? e.length !== t.length
? !1
: n.ignoreArrayOrder
? e.every((i) => t.some((s) => JSON.stringify(s) === JSON.stringify(i)))
: e.every((i, s) => JSON.stringify(i) === JSON.stringify(t[s]))
: typeof e == "object"
? JSON.stringify(e) === JSON.stringify(t)
: e === t;
}
function p(e) {
return !!e && typeof e == "object" && !Array.isArray(e);
}
function A(e) {
return e.some((t) => t.status !== r.EQUAL) ? r.UPDATED : r.EQUAL;
}
function E(e, t) {
if (!e) return { type: "object", status: r.isEqual, diff: [] };
let n = [];
return (
Object.entries(e).forEach(([i, s]) => {
if (p(s)) {
let f = [];
return (
Object.entries(s).forEach(([u, o]) => {
f.push({
name: u,
previousValue: t === r.ADDED ? void 0 : o,
currentValue: t === r.ADDED ? o : void 0,
status: t,
});
}),
n.push({
property: i,
previousValue: t === r.ADDED ? void 0 : e[i],
currentValue: t === r.ADDED ? s : void 0,
status: t,
subPropertiesDiff: f,
})
);
}
return n.push({
property: i,
previousValue: t === r.ADDED ? void 0 : e[i],
currentValue: t === r.ADDED ? s : void 0,
status: t,
});
}),
{ type: "object", status: t, diff: n }
);
}
function S(e, t, n) {
if (!e) return;
let i = Object.entries(e).find(([s]) => d(s, t, n));
return i ? i[1] : void 0;
}
function l(e, t, n) {
return d(e, t, n) ? r.EQUAL : r.UPDATED;
}
function j(e) {
return e.some((t) => t.status !== r.EQUAL) ? r.UPDATED : r.EQUAL;
}
function y(e, t) {
if (!e) return;
let n = Object.keys(e),
i = Object.keys(t),
s = n.filter((f) => !i.includes(f));
if (s.length > 0) return s.map((f) => ({ property: f, value: e[f] }));
}
function b(e, t, n) {
let i = [],
s,
f = y(e, t);
return (
f &&
f.forEach((u) => {
i.push({
name: u.property,
previousValue: u.value,
currentValue: void 0,
status: r.DELETED,
});
}),
Object.entries(t).forEach(([u, o]) => {
let D = S(e, u, n);
if (!D)
return i.push({
name: u,
previousValue: D,
currentValue: o,
status: !e || !(u in e) ? r.ADDED : D === o ? r.EQUAL : r.UPDATED,
});
if (p(o)) {
let a = b(D, o, n);
a && a.length > 0 && (s = a);
}
D &&
i.push({
name: u,
previousValue: D,
currentValue: o,
status: l(D, o, n),
...(!!s && { subDiff: s }),
});
}),
i
);
}
function g(e, t, n) {
if (!e && !t) return { type: "object", status: r.EQUAL, diff: [] };
if (!e) return E(t, r.ADDED);
if (!t) return E(e, r.DELETED);
let i = [];
Object.entries(t).forEach(([f, u]) => {
let o = e[f];
if (!o)
return i.push({
property: f,
previousValue: o,
currentValue: u,
status: f in e ? (o === u ? r.EQUAL : r.UPDATED) : r.ADDED,
});
if (p(u)) {
let D = b(o, u, n),
a = j(D);
return i.push({
property: f,
previousValue: o,
currentValue: u,
status: a,
...(a !== r.EQUAL && { subPropertiesDiff: D }),
});
}
return i.push({
property: f,
previousValue: o,
currentValue: u,
status: l(o, u, n),
});
});
let s = y(e, t);
return (
s &&
s.forEach((f) => {
i.push({
property: f.property,
previousValue: f.value,
currentValue: void 0,
status: r.DELETED,
});
}),
{ type: "object", status: A(i), diff: i }
);
}
function O(e, t) {
return {
type: "list",
status: t,
diff: e.map((n, i) => ({
value: n,
prevIndex: t === r.ADDED ? null : i,
newIndex: t === r.ADDED ? i : null,
indexDiff: null,
status: t,
})),
};
}
function L(e) {
return e.some((t) => t.status !== r.EQUAL) ? r.UPDATED : r.EQUAL;
}
var m = (e, t) => {
if (!e && !t) return { type: "list", status: r.EQUAL, diff: [] };
if (!e) return O(t, r.ADDED);
if (!t) return O(e, r.DELETED);
let n = [],
i = [];
return (
t.forEach((s, f) => {
let u = e.findIndex((D, a) => d(D, s) && !i.includes(a));
u > -1 && i.push(u);
let o = u === -1 ? null : f - u;
return o === 0
? n.push({
value: s,
prevIndex: u,
newIndex: f,
indexDiff: o,
status: r.EQUAL,
})
: u === -1
? n.push({
value: s,
prevIndex: null,
newIndex: f,
indexDiff: o,
status: r.ADDED,
})
: n.push({
value: s,
prevIndex: u,
newIndex: f,
indexDiff: o,
status: r.MOVED,
});
}),
e.forEach((s, f) => {
if (!i.includes(f))
return n.splice(f, 0, {
value: s,
prevIndex: f,
newIndex: null,
indexDiff: null,
status: r.DELETED,
});
}),
{ type: "list", status: L(n), diff: n }
);
};
var u={ADDED:"added",EQUAL:"equal",DELETED:"deleted",UPDATED:"updated"},d={...u,MOVED:"moved"},c={BASIC:"basic",DEEP:"deep"};function p(t,e,f={ignoreArrayOrder:!1}){return typeof t!=typeof e?!1:Array.isArray(t)?t.length!==e.length?!1:f.ignoreArrayOrder?t.every(r=>e.some(s=>JSON.stringify(s)===JSON.stringify(r))):t.every((r,s)=>JSON.stringify(r)===JSON.stringify(e[s])):typeof t=="object"?JSON.stringify(t)===JSON.stringify(e):t===e}function l(t){return !!t&&typeof t=="object"&&!Array.isArray(t)}function O(t,e={statuses:[],granularity:c.BASIC}){let{statuses:f,granularity:r}=e;return t.reduce((s,i)=>{if(r===c.DEEP&&i.subPropertiesDiff){let n=O(i.subPropertiesDiff,e);if(n.length>0)return [...s,{...i,subPropertiesDiff:n}]}if(r===c.DEEP&&i.subDiff){let n=O(i.subDiff,e);if(n.length>0)return [...s,{...i,subDiff:n}]}return f.includes(i.status)?[...s,i]:s},[])}function b(t){return t.some(e=>e.status!==u.EQUAL)?u.UPDATED:u.EQUAL}function E(t,e,f={ignoreArrayOrder:!1,showOnly:{statuses:[],granularity:c.BASIC}}){if(!t)return {type:"object",status:u.EQUAL,diff:[]};let r=[];return Object.entries(t).forEach(([s,i])=>{if(l(i)){let n=[];return Object.entries(i).forEach(([o,a])=>{n.push({property:o,previousValue:e===u.ADDED?void 0:a,currentValue:e===u.ADDED?a:void 0,status:e});}),r.push({property:s,previousValue:e===u.ADDED?void 0:t[s],currentValue:e===u.ADDED?i:void 0,status:e,subPropertiesDiff:n})}return r.push({property:s,previousValue:e===u.ADDED?void 0:t[s],currentValue:e===u.ADDED?i:void 0,status:e})}),f.showOnly&&f.showOnly.statuses.length>0?{type:"object",status:e,diff:O(r,f.showOnly)}:{type:"object",status:e,diff:r}}function T(t,e,f){if(!t)return;let r=Object.entries(t).find(([s])=>p(s,e,f));return r?r[1]:void 0}function A(t,e,f){return p(t,e,f)?u.EQUAL:u.UPDATED}function m(t){return t.some(e=>e.status!==u.EQUAL)?u.UPDATED:u.EQUAL}function j(t,e){if(!t)return;let f=Object.keys(t),r=Object.keys(e),s=f.filter(i=>!r.includes(i));if(s.length>0)return s.map(i=>({property:i,value:t[i]}))}function L(t,e,f){let r=[],s,i=j(t,e);return i&&i.forEach(n=>{r.push({property:n.property,previousValue:n.value,currentValue:void 0,status:u.DELETED});}),Object.entries(e).forEach(([n,o])=>{let a=T(t,n,f);if(!a)return r.push({property:n,previousValue:a,currentValue:o,status:!t||!(n in t)?u.ADDED:a===o?u.EQUAL:u.UPDATED});if(l(o)){let D=L(a,o,f);D&&D.length>0&&(s=D);}a&&r.push({property:n,previousValue:a,currentValue:o,status:A(a,o,f),...!!s&&{subDiff:s}});}),r}function U(t,e,f={ignoreArrayOrder:!1,showOnly:{statuses:[],granularity:c.BASIC}}){if(!t&&!e)return {type:"object",status:u.EQUAL,diff:[]};if(!t)return E(e,u.ADDED,f);if(!e)return E(t,u.DELETED,f);let r=[];Object.entries(e).forEach(([i,n])=>{let o=t[i];if(!o)return r.push({property:i,previousValue:o,currentValue:n,status:i in t?o===n?u.EQUAL:u.UPDATED:u.ADDED});if(l(n)){let a=L(o,n,f),D=m(a);return r.push({property:i,previousValue:o,currentValue:n,status:D,...D!==u.EQUAL&&{subPropertiesDiff:a}})}return r.push({property:i,previousValue:o,currentValue:n,status:A(o,n,f)})});let s=j(t,e);return s&&s.forEach(i=>{r.push({property:i.property,previousValue:i.value,currentValue:void 0,status:u.DELETED});}),f.showOnly&&f.showOnly.statuses.length>0?{type:"object",status:b(r),diff:O(r,f.showOnly)}:{type:"object",status:b(r),diff:r}}function P(t,e=[]){return t.filter(f=>e?.includes(f.status))}function S(t,e,f={showOnly:[]}){let r=t.map((s,i)=>({value:s,prevIndex:e===d.ADDED?null:i,newIndex:e===d.ADDED?i:null,indexDiff:null,status:e}));return f.showOnly&&f.showOnly.length>0?{type:"list",status:e,diff:r.filter(s=>f.showOnly?.includes(s.status))}:{type:"list",status:e,diff:r}}function g(t){return t.some(e=>e.status!==d.EQUAL)?d.UPDATED:d.EQUAL}var w=(t,e,f={showOnly:[]})=>{if(!t&&!e)return {type:"list",status:d.EQUAL,diff:[]};if(!t)return S(e,d.ADDED,f);if(!e)return S(t,d.DELETED,f);let r=[],s=[];return e.forEach((i,n)=>{let o=t.findIndex((D,h)=>p(D,i)&&!s.includes(h));o>-1&&s.push(o);let a=o===-1?null:n-o;return a===0?r.push({value:i,prevIndex:o,newIndex:n,indexDiff:a,status:d.EQUAL}):o===-1?r.push({value:i,prevIndex:null,newIndex:n,indexDiff:a,status:d.ADDED}):r.push({value:i,prevIndex:o,newIndex:n,indexDiff:a,status:d.MOVED})}),t.forEach((i,n)=>{if(!s.includes(n))return r.splice(n,0,{value:i,prevIndex:n,newIndex:null,indexDiff:null,status:d.DELETED})}),f.showOnly&&f?.showOnly?.length>0?{type:"list",status:g(r),diff:P(r,f.showOnly)}:{type:"list",status:g(r),diff:r}};
exports.getListDiff = m;
exports.getObjectDiff = g;
exports.isEqual = d;
exports.isObject = p;
exports.getListDiff = w;
exports.getObjectDiff = U;
exports.isEqual = p;
exports.isObject = l;

231
dist/index.mjs vendored

@ -1,230 +1,3 @@ @@ -1,230 +1,3 @@
var r = {
ADDED: "added",
EQUAL: "equal",
MOVED: "moved",
DELETED: "deleted",
UPDATED: "updated",
};
function d(e, t, n = { ignoreArrayOrder: !1 }) {
return typeof e != typeof t
? !1
: Array.isArray(e)
? e.length !== t.length
? !1
: n.ignoreArrayOrder
? e.every((i) => t.some((s) => JSON.stringify(s) === JSON.stringify(i)))
: e.every((i, s) => JSON.stringify(i) === JSON.stringify(t[s]))
: typeof e == "object"
? JSON.stringify(e) === JSON.stringify(t)
: e === t;
}
function p(e) {
return !!e && typeof e == "object" && !Array.isArray(e);
}
function S(e) {
return e.some((t) => t.status !== r.EQUAL) ? r.UPDATED : r.EQUAL;
}
function l(e, t) {
if (!e) return { type: "object", status: r.isEqual, diff: [] };
let n = [];
return (
Object.entries(e).forEach(([i, s]) => {
if (p(s)) {
let f = [];
return (
Object.entries(s).forEach(([u, o]) => {
f.push({
name: u,
previousValue: t === r.ADDED ? void 0 : o,
currentValue: t === r.ADDED ? o : void 0,
status: t,
});
}),
n.push({
property: i,
previousValue: t === r.ADDED ? void 0 : e[i],
currentValue: t === r.ADDED ? s : void 0,
status: t,
subPropertiesDiff: f,
})
);
}
return n.push({
property: i,
previousValue: t === r.ADDED ? void 0 : e[i],
currentValue: t === r.ADDED ? s : void 0,
status: t,
});
}),
{ type: "object", status: t, diff: n }
);
}
function j(e, t, n) {
if (!e) return;
let i = Object.entries(e).find(([s]) => d(s, t, n));
return i ? i[1] : void 0;
}
function y(e, t, n) {
return d(e, t, n) ? r.EQUAL : r.UPDATED;
}
function g(e) {
return e.some((t) => t.status !== r.EQUAL) ? r.UPDATED : r.EQUAL;
}
function b(e, t) {
if (!e) return;
let n = Object.keys(e),
i = Object.keys(t),
s = n.filter((f) => !i.includes(f));
if (s.length > 0) return s.map((f) => ({ property: f, value: e[f] }));
}
function O(e, t, n) {
let i = [],
s,
f = b(e, t);
return (
f &&
f.forEach((u) => {
i.push({
name: u.property,
previousValue: u.value,
currentValue: void 0,
status: r.DELETED,
});
}),
Object.entries(t).forEach(([u, o]) => {
let D = j(e, u, n);
if (!D)
return i.push({
name: u,
previousValue: D,
currentValue: o,
status: !e || !(u in e) ? r.ADDED : D === o ? r.EQUAL : r.UPDATED,
});
if (p(o)) {
let a = O(D, o, n);
a && a.length > 0 && (s = a);
}
D &&
i.push({
name: u,
previousValue: D,
currentValue: o,
status: y(D, o, n),
...(!!s && { subDiff: s }),
});
}),
i
);
}
function L(e, t, n) {
if (!e && !t) return { type: "object", status: r.EQUAL, diff: [] };
if (!e) return l(t, r.ADDED);
if (!t) return l(e, r.DELETED);
let i = [];
Object.entries(t).forEach(([f, u]) => {
let o = e[f];
if (!o)
return i.push({
property: f,
previousValue: o,
currentValue: u,
status: f in e ? (o === u ? r.EQUAL : r.UPDATED) : r.ADDED,
});
if (p(u)) {
let D = O(o, u, n),
a = g(D);
return i.push({
property: f,
previousValue: o,
currentValue: u,
status: a,
...(a !== r.EQUAL && { subPropertiesDiff: D }),
});
}
return i.push({
property: f,
previousValue: o,
currentValue: u,
status: y(o, u, n),
});
});
let s = b(e, t);
return (
s &&
s.forEach((f) => {
i.push({
property: f.property,
previousValue: f.value,
currentValue: void 0,
status: r.DELETED,
});
}),
{ type: "object", status: S(i), diff: i }
);
}
function A(e, t) {
return {
type: "list",
status: t,
diff: e.map((n, i) => ({
value: n,
prevIndex: t === r.ADDED ? null : i,
newIndex: t === r.ADDED ? i : null,
indexDiff: null,
status: t,
})),
};
}
function m(e) {
return e.some((t) => t.status !== r.EQUAL) ? r.UPDATED : r.EQUAL;
}
var h = (e, t) => {
if (!e && !t) return { type: "list", status: r.EQUAL, diff: [] };
if (!e) return A(t, r.ADDED);
if (!t) return A(e, r.DELETED);
let n = [],
i = [];
return (
t.forEach((s, f) => {
let u = e.findIndex((D, a) => d(D, s) && !i.includes(a));
u > -1 && i.push(u);
let o = u === -1 ? null : f - u;
return o === 0
? n.push({
value: s,
prevIndex: u,
newIndex: f,
indexDiff: o,
status: r.EQUAL,
})
: u === -1
? n.push({
value: s,
prevIndex: null,
newIndex: f,
indexDiff: o,
status: r.ADDED,
})
: n.push({
value: s,
prevIndex: u,
newIndex: f,
indexDiff: o,
status: r.MOVED,
});
}),
e.forEach((s, f) => {
if (!i.includes(f))
return n.splice(f, 0, {
value: s,
prevIndex: f,
newIndex: null,
indexDiff: null,
status: r.DELETED,
});
}),
{ type: "list", status: m(n), diff: n }
);
};
var u={ADDED:"added",EQUAL:"equal",DELETED:"deleted",UPDATED:"updated"},d={...u,MOVED:"moved"},c={BASIC:"basic",DEEP:"deep"};function p(t,e,f={ignoreArrayOrder:!1}){return typeof t!=typeof e?!1:Array.isArray(t)?t.length!==e.length?!1:f.ignoreArrayOrder?t.every(r=>e.some(s=>JSON.stringify(s)===JSON.stringify(r))):t.every((r,s)=>JSON.stringify(r)===JSON.stringify(e[s])):typeof t=="object"?JSON.stringify(t)===JSON.stringify(e):t===e}function l(t){return !!t&&typeof t=="object"&&!Array.isArray(t)}function b(t,e={statuses:[],granularity:c.BASIC}){let{statuses:f,granularity:r}=e;return t.reduce((s,i)=>{if(r===c.DEEP&&i.subPropertiesDiff){let n=b(i.subPropertiesDiff,e);if(n.length>0)return [...s,{...i,subPropertiesDiff:n}]}if(r===c.DEEP&&i.subDiff){let n=b(i.subDiff,e);if(n.length>0)return [...s,{...i,subDiff:n}]}return f.includes(i.status)?[...s,i]:s},[])}function E(t){return t.some(e=>e.status!==u.EQUAL)?u.UPDATED:u.EQUAL}function A(t,e,f={ignoreArrayOrder:!1,showOnly:{statuses:[],granularity:c.BASIC}}){if(!t)return {type:"object",status:u.EQUAL,diff:[]};let r=[];return Object.entries(t).forEach(([s,i])=>{if(l(i)){let n=[];return Object.entries(i).forEach(([o,a])=>{n.push({property:o,previousValue:e===u.ADDED?void 0:a,currentValue:e===u.ADDED?a:void 0,status:e});}),r.push({property:s,previousValue:e===u.ADDED?void 0:t[s],currentValue:e===u.ADDED?i:void 0,status:e,subPropertiesDiff:n})}return r.push({property:s,previousValue:e===u.ADDED?void 0:t[s],currentValue:e===u.ADDED?i:void 0,status:e})}),f.showOnly&&f.showOnly.statuses.length>0?{type:"object",status:e,diff:b(r,f.showOnly)}:{type:"object",status:e,diff:r}}function m(t,e,f){if(!t)return;let r=Object.entries(t).find(([s])=>p(s,e,f));return r?r[1]:void 0}function j(t,e,f){return p(t,e,f)?u.EQUAL:u.UPDATED}function U(t){return t.some(e=>e.status!==u.EQUAL)?u.UPDATED:u.EQUAL}function L(t,e){if(!t)return;let f=Object.keys(t),r=Object.keys(e),s=f.filter(i=>!r.includes(i));if(s.length>0)return s.map(i=>({property:i,value:t[i]}))}function S(t,e,f){let r=[],s,i=L(t,e);return i&&i.forEach(n=>{r.push({property:n.property,previousValue:n.value,currentValue:void 0,status:u.DELETED});}),Object.entries(e).forEach(([n,o])=>{let a=m(t,n,f);if(!a)return r.push({property:n,previousValue:a,currentValue:o,status:!t||!(n in t)?u.ADDED:a===o?u.EQUAL:u.UPDATED});if(l(o)){let D=S(a,o,f);D&&D.length>0&&(s=D);}a&&r.push({property:n,previousValue:a,currentValue:o,status:j(a,o,f),...!!s&&{subDiff:s}});}),r}function P(t,e,f={ignoreArrayOrder:!1,showOnly:{statuses:[],granularity:c.BASIC}}){if(!t&&!e)return {type:"object",status:u.EQUAL,diff:[]};if(!t)return A(e,u.ADDED,f);if(!e)return A(t,u.DELETED,f);let r=[];Object.entries(e).forEach(([i,n])=>{let o=t[i];if(!o)return r.push({property:i,previousValue:o,currentValue:n,status:i in t?o===n?u.EQUAL:u.UPDATED:u.ADDED});if(l(n)){let a=S(o,n,f),D=U(a);return r.push({property:i,previousValue:o,currentValue:n,status:D,...D!==u.EQUAL&&{subPropertiesDiff:a}})}return r.push({property:i,previousValue:o,currentValue:n,status:j(o,n,f)})});let s=L(t,e);return s&&s.forEach(i=>{r.push({property:i.property,previousValue:i.value,currentValue:void 0,status:u.DELETED});}),f.showOnly&&f.showOnly.statuses.length>0?{type:"object",status:E(r),diff:b(r,f.showOnly)}:{type:"object",status:E(r),diff:r}}function w(t,e=[]){return t.filter(f=>e?.includes(f.status))}function g(t,e,f={showOnly:[]}){let r=t.map((s,i)=>({value:s,prevIndex:e===d.ADDED?null:i,newIndex:e===d.ADDED?i:null,indexDiff:null,status:e}));return f.showOnly&&f.showOnly.length>0?{type:"list",status:e,diff:r.filter(s=>f.showOnly?.includes(s.status))}:{type:"list",status:e,diff:r}}function h(t){return t.some(e=>e.status!==d.EQUAL)?d.UPDATED:d.EQUAL}var I=(t,e,f={showOnly:[]})=>{if(!t&&!e)return {type:"list",status:d.EQUAL,diff:[]};if(!t)return g(e,d.ADDED,f);if(!e)return g(t,d.DELETED,f);let r=[],s=[];return e.forEach((i,n)=>{let o=t.findIndex((D,T)=>p(D,i)&&!s.includes(T));o>-1&&s.push(o);let a=o===-1?null:n-o;return a===0?r.push({value:i,prevIndex:o,newIndex:n,indexDiff:a,status:d.EQUAL}):o===-1?r.push({value:i,prevIndex:null,newIndex:n,indexDiff:a,status:d.ADDED}):r.push({value:i,prevIndex:o,newIndex:n,indexDiff:a,status:d.MOVED})}),t.forEach((i,n)=>{if(!s.includes(n))return r.splice(n,0,{value:i,prevIndex:n,newIndex:null,indexDiff:null,status:d.DELETED})}),f.showOnly&&f?.showOnly?.length>0?{type:"list",status:h(r),diff:w(r,f.showOnly)}:{type:"list",status:h(r),diff:r}};
export { h as getListDiff, L as getObjectDiff, d as isEqual, p as isObject };
export { I as getListDiff, P as getObjectDiff, p as isEqual, l as isObject };

12
package.json

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
{
"name": "@donedeal0/superdiff",
"version": "1.0.8",
"version": "1.0.9",
"description": "SuperDiff checks the changes between two objects or arrays. It returns a complete diff with relevant information for each property or piece of data",
"main": "dist/index.js",
"module": "dist/index.js",
@ -37,10 +37,10 @@ @@ -37,10 +37,10 @@
},
"devDependencies": {
"@babel/preset-env": "^7.20.2",
"@types/jest": "^29.2.4",
"jest": "^29.3.1",
"ts-jest": "^29.0.3",
"tsup": "^6.5.0",
"typescript": "^4.9.4"
"@types/jest": "^29.4.0",
"jest": "^29.4.2",
"ts-jest": "^29.0.5",
"tsup": "^6.6.0",
"typescript": "^4.9.5"
}
}

39
src/list-diff.ts

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import {
LIST_STATUS,
ListDiff,
ListData,
ListDiff,
ListDiffStatus,
ListOptions,
} from "./model";
@ -16,18 +16,27 @@ function getLeanDiff( @@ -16,18 +16,27 @@ function getLeanDiff(
function formatSingleListDiff(
listData: ListData[],
status: ListDiffStatus
status: ListDiffStatus,
options: ListOptions = { showOnly: [] }
): ListDiff {
const diff = listData.map((data: ListData, i) => ({
value: data,
prevIndex: status === LIST_STATUS.ADDED ? null : i,
newIndex: status === LIST_STATUS.ADDED ? i : null,
indexDiff: null,
status,
}));
if (options.showOnly && options.showOnly.length > 0) {
return {
type: "list",
status,
diff: diff.filter((value) => options.showOnly?.includes(value.status)),
};
}
return {
type: "list",
status,
diff: listData.map((data: ListData, i) => ({
value: data,
prevIndex: status === LIST_STATUS.ADDED ? null : i,
newIndex: status === LIST_STATUS.ADDED ? i : null,
indexDiff: null,
status,
})),
diff,
};
}
@ -50,10 +59,18 @@ export const getListDiff = ( @@ -50,10 +59,18 @@ export const getListDiff = (
};
}
if (!prevList) {
return formatSingleListDiff(nextList as ListData, LIST_STATUS.ADDED);
return formatSingleListDiff(
nextList as ListData,
LIST_STATUS.ADDED,
options
);
}
if (!nextList) {
return formatSingleListDiff(prevList as ListData, LIST_STATUS.DELETED);
return formatSingleListDiff(
prevList as ListData,
LIST_STATUS.DELETED,
options
);
}
const diff: ListDiff["diff"] = [];
const prevIndexMatches: number[] = [];

10
src/model.ts

@ -10,6 +10,11 @@ export const LIST_STATUS: Record<string, ListDiffStatus> = { @@ -10,6 +10,11 @@ export const LIST_STATUS: Record<string, ListDiffStatus> = {
MOVED: "moved",
};
export const GRANULARITY: Record<string, "basic" | "deep"> = {
BASIC: "basic",
DEEP: "deep",
};
export type ListDiffStatus =
| "added"
| "equal"
@ -30,7 +35,8 @@ export type ListStatusTuple = readonly [ @@ -30,7 +35,8 @@ export type ListStatusTuple = readonly [
"added",
"equal",
"deleted",
"moved" | "updated"
"moved",
"updated"
];
export type isEqualOptions = {
@ -41,7 +47,7 @@ export type ObjectOptions = { @@ -41,7 +47,7 @@ export type ObjectOptions = {
ignoreArrayOrder?: boolean;
showOnly?: {
statuses: Array<ObjectStatusTuple[number]>;
granularity?: "basic" | "deep";
granularity?: typeof GRANULARITY[keyof typeof GRANULARITY];
};
};

43
src/object-diff.ts

@ -1,23 +1,24 @@ @@ -1,23 +1,24 @@
import {
GRANULARITY,
STATUS,
ObjectData,
ObjectDiff,
STATUS,
SubProperties,
ObjectOptions,
ObjectDiffStatus,
ObjectOptions,
SubProperties,
} from "./model";
import { isObject, isEqual } from "./utils";
import { isEqual, isObject } from "./utils";
function getLeanDiff(
diff: ObjectDiff["diff"],
showOnly: ObjectOptions["showOnly"] = { statuses: [], granularity: "basic" }
showOnly: ObjectOptions["showOnly"] = {
statuses: [],
granularity: GRANULARITY.BASIC,
}
): ObjectDiff["diff"] {
const { statuses, granularity } = showOnly;
return diff.reduce((acc, value) => {
if (statuses.includes(value.status)) {
return [...acc, value];
}
if (granularity === "deep" && value.subPropertiesDiff) {
if (granularity === GRANULARITY.DEEP && value.subPropertiesDiff) {
const cleanSubPropertiesDiff = getLeanDiff(
value.subPropertiesDiff,
showOnly
@ -30,13 +31,16 @@ function getLeanDiff( @@ -30,13 +31,16 @@ function getLeanDiff(
}
}
// @ts-ignore
if (granularity === "deep" && value.subDiff) {
if (granularity === GRANULARITY.DEEP && value.subDiff) {
// @ts-ignore
const cleanSubDiff = getLeanDiff(value.subDiff, showOnly);
if (cleanSubDiff.length > 0) {
return [...acc, { ...value, subDiff: cleanSubDiff }];
}
}
if (statuses.includes(value.status)) {
return [...acc, value];
}
return acc;
}, [] as ObjectDiff["diff"]);
}
@ -49,7 +53,11 @@ function getObjectStatus(diff: ObjectDiff["diff"]): ObjectDiffStatus { @@ -49,7 +53,11 @@ function getObjectStatus(diff: ObjectDiff["diff"]): ObjectDiffStatus {
function formatSingleObjectDiff(
data: ObjectData,
status: ObjectDiffStatus
status: ObjectDiffStatus,
options: ObjectOptions = {
ignoreArrayOrder: false,
showOnly: { statuses: [], granularity: GRANULARITY.BASIC },
}
): ObjectDiff {
if (!data) {
return {
@ -85,6 +93,13 @@ function formatSingleObjectDiff( @@ -85,6 +93,13 @@ function formatSingleObjectDiff(
status,
});
});
if (options.showOnly && options.showOnly.statuses.length > 0) {
return {
type: "object",
status,
diff: getLeanDiff(diff, options.showOnly),
};
}
return {
type: "object",
status,
@ -210,7 +225,7 @@ export function getObjectDiff( @@ -210,7 +225,7 @@ export function getObjectDiff(
nextData: ObjectData,
options: ObjectOptions = {
ignoreArrayOrder: false,
showOnly: { statuses: [], granularity: "basic" },
showOnly: { statuses: [], granularity: GRANULARITY.BASIC },
}
): ObjectDiff {
if (!prevData && !nextData) {
@ -221,10 +236,10 @@ export function getObjectDiff( @@ -221,10 +236,10 @@ export function getObjectDiff(
};
}
if (!prevData) {
return formatSingleObjectDiff(nextData, STATUS.ADDED);
return formatSingleObjectDiff(nextData, STATUS.ADDED, options);
}
if (!nextData) {
return formatSingleObjectDiff(prevData, STATUS.DELETED);
return formatSingleObjectDiff(prevData, STATUS.DELETED, options);
}
const diff: ObjectDiff["diff"] = [];
Object.entries(nextData).forEach(([nextProperty, nextValue]) => {

56
test/list-diff.test.ts

@ -453,4 +453,60 @@ describe("getListDiff", () => { @@ -453,4 +453,60 @@ describe("getListDiff", () => {
],
});
});
it("returns an empty diff if no property match the required statuses output", () => {
expect(getListDiff(null, null)).toStrictEqual({
type: "list",
status: "equal",
diff: [],
});
expect(
getListDiff(["mbappe", "mendes", "verratti", "ruiz"], null, {
showOnly: ["moved", "updated"],
})
).toStrictEqual({
type: "list",
status: "deleted",
diff: [],
});
});
it("returns all values if their status match the required statuses", () => {
expect(
getListDiff(null, ["mbappe", "mendes", "verratti", "ruiz"], {
showOnly: ["added"],
})
).toStrictEqual({
type: "list",
status: "added",
diff: [
{
value: "mbappe",
prevIndex: null,
newIndex: 0,
indexDiff: null,
status: "added",
},
{
value: "mendes",
prevIndex: null,
newIndex: 1,
indexDiff: null,
status: "added",
},
{
value: "verratti",
prevIndex: null,
newIndex: 2,
indexDiff: null,
status: "added",
},
{
value: "ruiz",
prevIndex: null,
newIndex: 3,
indexDiff: null,
status: "added",
},
],
});
});
});

288
test/object-diff.test.ts

@ -462,7 +462,7 @@ describe("getObjectDiff", () => { @@ -462,7 +462,7 @@ describe("getObjectDiff", () => {
],
});
});
it("showOnly main added values", () => {
it("shows only main added values", () => {
expect(
getObjectDiff(
{
@ -500,7 +500,7 @@ describe("getObjectDiff", () => { @@ -500,7 +500,7 @@ describe("getObjectDiff", () => {
],
});
});
it("showOnly added and deleted values in depth", () => {
it("shows only added and deleted values in nested objects", () => {
expect(
getObjectDiff(
{
@ -574,4 +574,288 @@ describe("getObjectDiff", () => { @@ -574,4 +574,288 @@ describe("getObjectDiff", () => {
],
});
});
it("shows only updated values in deeply nested objects", () => {
expect(
getObjectDiff(
{
id: 54,
user: {
name: "joe",
data: {
member: true,
hobbies: {
football: ["psg"],
rugby: ["france"],
},
},
},
},
{
id: 54,
user: {
name: "joe",
data: {
member: true,
hobbies: {
football: ["psg", "nantes"],
golf: ["st andrews"],
},
},
},
},
{
showOnly: {
statuses: ["updated"],
granularity: "deep",
},
}
)
).toStrictEqual({
type: "object",
status: "updated",
diff: [
{
property: "user",
previousValue: {
name: "joe",
data: {
member: true,
hobbies: {
football: ["psg"],
rugby: ["france"],
},
},
},
currentValue: {
name: "joe",
data: {
member: true,
hobbies: {
football: ["psg", "nantes"],
golf: ["st andrews"],
},
},
},
status: "updated",
subPropertiesDiff: [
{
property: "data",
previousValue: {
member: true,
hobbies: {
football: ["psg"],
rugby: ["france"],
},
},
currentValue: {
member: true,
hobbies: {
football: ["psg", "nantes"],
golf: ["st andrews"],
},
},
status: "updated",
subDiff: [
{
property: "hobbies",
previousValue: {
football: ["psg"],
rugby: ["france"],
},
currentValue: {
football: ["psg", "nantes"],
golf: ["st andrews"],
},
status: "updated",
subDiff: [
{
property: "football",
previousValue: ["psg"],
currentValue: ["psg", "nantes"],
status: "updated",
},
],
},
],
},
],
},
],
});
});
it("shows only added values in deeply nested objects", () => {
expect(
getObjectDiff(
{
id: 54,
user: {
name: "joe",
data: {
member: true,
hobbies: {
rugby: ["france"],
},
},
},
},
{
id: 54,
user: {
name: "joe",
data: {
member: true,
hobbies: {
football: ["psg", "nantes"],
golf: ["st andrews"],
},
},
},
},
{
showOnly: {
statuses: ["added"],
granularity: "deep",
},
}
)
).toStrictEqual({
type: "object",
status: "updated",
diff: [
{
property: "user",
previousValue: {
name: "joe",
data: {
member: true,
hobbies: {
rugby: ["france"],
},
},
},
currentValue: {
name: "joe",
data: {
member: true,
hobbies: {
football: ["psg", "nantes"],
golf: ["st andrews"],
},
},
},
status: "updated",
subPropertiesDiff: [
{
property: "data",
previousValue: {
member: true,
hobbies: {
rugby: ["france"],
},
},
currentValue: {
member: true,
hobbies: {
football: ["psg", "nantes"],
golf: ["st andrews"],
},
},
status: "updated",
subDiff: [
{
property: "hobbies",
previousValue: {
rugby: ["france"],
},
currentValue: {
football: ["psg", "nantes"],
golf: ["st andrews"],
},
status: "updated",
subDiff: [
{
property: "football",
previousValue: undefined,
currentValue: ["psg", "nantes"],
status: "added",
},
{
property: "golf",
previousValue: undefined,
currentValue: ["st andrews"],
status: "added",
},
],
},
],
},
],
},
],
});
});
it("returns an empty diff if no property match the required statuses output", () => {
expect(
getObjectDiff(
null,
{
name: "joe",
age: 54,
hobbies: ["golf", "football"],
},
{ showOnly: { statuses: ["deleted"], granularity: "deep" } }
)
).toStrictEqual({
type: "object",
status: "added",
diff: [],
});
});
expect(
getObjectDiff(
{
name: "joe",
age: 54,
hobbies: ["golf", "football"],
},
null,
{ showOnly: { statuses: ["added"], granularity: "deep" } }
)
).toStrictEqual({
type: "object",
status: "deleted",
diff: [],
});
it("returns all values if their status match the required statuses", () => {
expect(
getObjectDiff(
{ name: "joe", age: 54, hobbies: ["golf", "football"] },
null,
{ showOnly: { statuses: ["deleted"] } }
)
).toStrictEqual({
type: "object",
status: "deleted",
diff: [
{
property: "name",
previousValue: "joe",
currentValue: undefined,
status: "deleted",
},
{
property: "age",
previousValue: 54,
currentValue: undefined,
status: "deleted",
},
{
property: "hobbies",
previousValue: ["golf", "football"],
currentValue: undefined,
status: "deleted",
},
],
});
});
});

1056
yarn.lock

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save