Browse Source

chore: test stream-list-diff

pull/26/head
Antoine Lanoe 7 months ago
parent
commit
7fe7a84082
  1. 90
      src/lib/stream-list-diff/index.ts
  2. 374
      src/lib/stream-list-diff/stream-list-diff.test.ts

90
src/lib/stream-list-diff/index.ts

@ -6,7 +6,7 @@ import { @@ -6,7 +6,7 @@ import {
StreamReferences,
} from "@models/stream";
import { LIST_STATUS } from "@models/list";
import { isEqual, isObject } from "@lib/utils";
import { isObject } from "@lib/utils";
import { Emitter, EventEmitter, StreamEvent } from "./emitter";
function outputDiffChunk<T extends Record<string, unknown>>(
@ -31,6 +31,8 @@ function outputDiffChunk<T extends Record<string, unknown>>( @@ -31,6 +31,8 @@ function outputDiffChunk<T extends Record<string, unknown>>(
const output = chunks;
chunks = [];
return emitter.emit(StreamEvent.Data, output);
} else {
return;
}
}
return emitter.emit(StreamEvent.Data, [chunk]);
@ -73,56 +75,53 @@ function isValidChunkSize( @@ -73,56 +75,53 @@ function isValidChunkSize(
chunksSize: ListStreamOptions["chunksSize"],
): boolean {
if (!chunksSize) return true;
const x = String(Math.sign(chunksSize));
return x !== "-1" && x !== "NaN";
const sign = String(Math.sign(chunksSize));
return sign !== "-1" && sign !== "NaN";
}
function isDataValid<T extends Record<string, unknown>>(
data: T,
referenceProperty: ReferenceProperty<T>,
emitter: Emitter<T>,
listType: "prevList" | "nextList",
): boolean {
): { isValid: boolean; message?: string } {
if (!isObject(data)) {
emitter.emit(
StreamEvent.Error,
new Error(
`Your ${listType} must only contain valid objects. Found ${data}`,
),
);
return false;
return {
isValid: false,
message: `Your ${listType} must only contain valid objects. Found '${data}'`,
};
}
if (!Object.hasOwn(data, referenceProperty)) {
emitter.emit(
StreamEvent.Error,
new Error(
`The reference property ${String(referenceProperty)} is not available in all the objects of your ${listType}.`,
),
);
return false;
return {
isValid: false,
message: `The reference property '${String(referenceProperty)}' is not available in all the objects of your ${listType}.`,
};
}
return true;
return {
isValid: true,
message: "",
};
}
function getDiffChunks<T extends Record<string, unknown>>(
prevList: T[],
nextList: T[],
prevList: T[] = [],
nextList: T[] = [],
referenceProperty: ReferenceProperty<T>,
emitter: Emitter<T>,
options: ListStreamOptions = DEFAULT_LIST_STREAM_OPTIONS,
) {
): void {
if (!isValidChunkSize(options?.chunksSize)) {
return emitter.emit(
StreamEvent.Error,
new Error(
`The chunk size can't be negative. You entered the value ${options.chunksSize}`,
`The chunk size can't be negative. You entered the value '${options.chunksSize}'`,
),
);
}
if (!prevList && !nextList) {
return [];
if (prevList.length === 0 && nextList.length === 0) {
return emitter.emit(StreamEvent.Finish);
}
if (!prevList) {
const handleDiffChunk = outputDiffChunk<T>(emitter);
if (prevList.length === 0) {
const nextDiff = formatSingleListStreamDiff(
nextList as T[],
false,
@ -136,11 +135,12 @@ function getDiffChunks<T extends Record<string, unknown>>( @@ -136,11 +135,12 @@ function getDiffChunks<T extends Record<string, unknown>>(
);
emitter.emit(StreamEvent.Finish);
}
return nextDiff?.forEach((data, i) =>
nextDiff?.forEach((data, i) =>
handleDiffChunk(data, i === nextDiff.length - 1, options),
);
return emitter.emit(StreamEvent.Finish);
}
if (!nextList) {
if (nextList.length === 0) {
const prevDiff = formatSingleListStreamDiff(
prevList as T[],
true,
@ -154,17 +154,22 @@ function getDiffChunks<T extends Record<string, unknown>>( @@ -154,17 +154,22 @@ function getDiffChunks<T extends Record<string, unknown>>(
);
emitter.emit(StreamEvent.Finish);
}
return prevDiff?.forEach((data, i) =>
prevDiff?.forEach((data, i) =>
handleDiffChunk(data, i === prevDiff.length - 1, options),
);
return emitter.emit(StreamEvent.Finish);
}
const listsReferences: StreamReferences<T> = new Map();
const handleDiffChunk = outputDiffChunk<T>(emitter);
for (let i = 0; i < prevList.length; i++) {
const data = prevList[i];
if (data) {
const isValid = isDataValid(data, referenceProperty, emitter, "prevList");
const { isValid, message } = isDataValid(
data,
referenceProperty,
"prevList",
);
if (!isValid) {
emitter.emit(StreamEvent.Error, new Error(message));
emitter.emit(StreamEvent.Finish);
break;
}
@ -176,10 +181,15 @@ function getDiffChunks<T extends Record<string, unknown>>( @@ -176,10 +181,15 @@ function getDiffChunks<T extends Record<string, unknown>>(
}
for (let i = 0; i < nextList.length; i++) {
const data = prevList[i];
const data = nextList[i];
if (data) {
const isValid = isDataValid(data, referenceProperty, emitter, "nextList");
const { isValid, message } = isDataValid(
data,
referenceProperty,
"nextList",
);
if (!isValid) {
emitter.emit(StreamEvent.Error, new Error(message));
emitter.emit(StreamEvent.Finish);
break;
}
@ -207,10 +217,11 @@ function getDiffChunks<T extends Record<string, unknown>>( @@ -207,10 +217,11 @@ function getDiffChunks<T extends Record<string, unknown>>(
let streamedChunks = 0;
const totalChunks = listsReferences.size;
for (const data of listsReferences.values()) {
streamedChunks++;
const isLastChunk = totalChunks === streamedChunks;
if (!data.nextIndex) {
if (typeof data.nextIndex === "undefined") {
handleDiffChunk(
{
previousValue: prevList[data.prevIndex],
@ -226,17 +237,17 @@ function getDiffChunks<T extends Record<string, unknown>>( @@ -226,17 +237,17 @@ function getDiffChunks<T extends Record<string, unknown>>(
} else {
const prevData = prevList[data.prevIndex];
const nextData = nextList[data.nextIndex];
const isDataEqual = isEqual(prevData, nextData);
const indexDiff = data.prevIndex - data.nextIndex;
const isDataEqual = JSON.stringify(prevData) === JSON.stringify(nextData);
const indexDiff = data.nextIndex - data.prevIndex;
if (isDataEqual) {
if (indexDiff === 0) {
handleDiffChunk(
{
previousValue: prevList[data.prevIndex],
currentValue: nextList[data.nextIndex],
prevIndex: null,
prevIndex: data.prevIndex,
newIndex: data.nextIndex,
indexDiff: null,
indexDiff: 0,
status: LIST_STATUS.EQUAL,
},
isLastChunk,
@ -274,6 +285,7 @@ function getDiffChunks<T extends Record<string, unknown>>( @@ -274,6 +285,7 @@ function getDiffChunks<T extends Record<string, unknown>>(
}
}
}
return emitter.emit(StreamEvent.Finish);
}

374
src/lib/stream-list-diff/stream-list-diff.test.ts

@ -0,0 +1,374 @@ @@ -0,0 +1,374 @@
import { LIST_STATUS } from "@models/list";
import { streamListsDiff } from ".";
describe("streamListsDiff data", () => {
it("emits 'data' event and consider the all the nextList added if no prevList is provided", (done) => {
const nextList = [
{ id: 1, name: "Item 1" },
{ id: 2, name: "Item 2" },
];
const diff = streamListsDiff([], nextList, "id", { chunksSize: 2 });
const expectedChunks = [
{
previousValue: null,
currentValue: { id: 1, name: "Item 1" },
prevIndex: null,
newIndex: 0,
indexDiff: null,
status: LIST_STATUS.ADDED,
},
{
previousValue: null,
currentValue: { id: 2, name: "Item 2" },
prevIndex: null,
newIndex: 1,
indexDiff: null,
status: LIST_STATUS.ADDED,
},
];
diff.on("data", (chunk) => expect(chunk).toStrictEqual(expectedChunks));
diff.on("finish", () => done());
});
it("emits 'data' event and consider the all the prevList deleted if no nextList is provided", (done) => {
const prevList = [
{ id: 1, name: "Item 1" },
{ id: 2, name: "Item 2" },
];
const diff = streamListsDiff(prevList, [], "id", { chunksSize: 2 });
const expectedChunks = [
{
previousValue: { id: 1, name: "Item 1" },
currentValue: null,
prevIndex: 0,
newIndex: null,
indexDiff: null,
status: LIST_STATUS.DELETED,
},
{
previousValue: { id: 2, name: "Item 2" },
currentValue: null,
prevIndex: 1,
newIndex: null,
indexDiff: null,
status: LIST_STATUS.DELETED,
},
];
diff.on("data", (chunk) => expect(chunk).toStrictEqual(expectedChunks));
diff.on("finish", () => done());
});
it("emits 'data' event with one object diff by chunk if chunkSize is 0 or undefined", (done) => {
const prevList = [
{ id: 1, name: "Item 1" },
{ id: 2, name: "Item 2" },
];
const nextList = [
{ id: 2, name: "Item 2" },
{ id: 3, name: "Item 3" },
];
const diff = streamListsDiff(prevList, nextList, "id");
const expectedChunks = [
[
{
previousValue: null,
currentValue: { id: 3, name: "Item 3" },
prevIndex: null,
newIndex: 1,
indexDiff: null,
status: LIST_STATUS.ADDED,
},
],
[
{
previousValue: { id: 1, name: "Item 1" },
currentValue: null,
prevIndex: 0,
newIndex: null,
indexDiff: null,
status: LIST_STATUS.DELETED,
},
],
[
{
previousValue: { id: 2, name: "Item 2" },
currentValue: { id: 2, name: "Item 2" },
prevIndex: 1,
newIndex: 0,
indexDiff: -1,
status: LIST_STATUS.MOVED,
},
],
];
let chunkCount = 0;
diff.on("data", (chunk) => {
expect(chunk).toStrictEqual(expectedChunks[chunkCount]);
chunkCount++;
});
diff.on("finish", () => done());
});
it("emits 'data' event with 5 object diff by chunk", (done) => {
const prevList = [
{ id: 1, name: "Item 1" },
{ id: 2, name: "Item 2" },
{ id: 3, name: "Item 3" },
{ id: 4, name: "Item 4" },
{ id: 5, name: "Item 5" },
{ id: 6, name: "Item 6" },
{ id: 7, name: "Item 7" },
{ id: 8, name: "Item 8" },
{ id: 9, name: "Item 9" },
{ id: 10, name: "Item 10" },
];
const nextList = [
{ id: 1, name: "Item 1" },
{ id: 2, name: "Item Two" },
{ id: 3, name: "Item 3" },
{ id: 5, name: "Item 5" },
{ id: 6, name: "Item Six" },
{ id: 7, name: "Item 7" },
{ id: 10, name: "Item 10" },
{ id: 11, name: "Item 11" },
{ id: 9, name: "Item 9" },
{ id: 8, name: "Item 8" },
];
const diff = streamListsDiff(prevList, nextList, "id", { chunksSize: 5 });
const expectedChunks = [
[
{
previousValue: null,
currentValue: { id: 11, name: "Item 11" },
prevIndex: null,
newIndex: 7,
indexDiff: null,
status: LIST_STATUS.ADDED,
},
{
previousValue: { id: 1, name: "Item 1" },
currentValue: { id: 1, name: "Item 1" },
prevIndex: 0,
newIndex: 0,
indexDiff: 0,
status: LIST_STATUS.EQUAL,
},
{
previousValue: { id: 2, name: "Item 2" },
currentValue: { id: 2, name: "Item Two" },
prevIndex: 1,
newIndex: 1,
indexDiff: 0,
status: LIST_STATUS.UPDATED,
},
{
previousValue: { id: 3, name: "Item 3" },
currentValue: { id: 3, name: "Item 3" },
prevIndex: 2,
newIndex: 2,
indexDiff: 0,
status: LIST_STATUS.EQUAL,
},
{
previousValue: { id: 4, name: "Item 4" },
currentValue: null,
prevIndex: 3,
newIndex: null,
indexDiff: null,
status: LIST_STATUS.DELETED,
},
],
[
{
previousValue: { id: 5, name: "Item 5" },
currentValue: { id: 5, name: "Item 5" },
prevIndex: 4,
newIndex: 3,
indexDiff: -1,
status: LIST_STATUS.MOVED,
},
{
previousValue: { id: 6, name: "Item 6" },
currentValue: { id: 6, name: "Item Six" },
prevIndex: 5,
newIndex: 4,
indexDiff: -1,
status: LIST_STATUS.UPDATED,
},
{
previousValue: { id: 7, name: "Item 7" },
currentValue: { id: 7, name: "Item 7" },
prevIndex: 6,
newIndex: 5,
indexDiff: -1,
status: LIST_STATUS.MOVED,
},
{
previousValue: { id: 8, name: "Item 8" },
currentValue: { id: 8, name: "Item 8" },
prevIndex: 7,
newIndex: 9,
indexDiff: 2,
status: LIST_STATUS.MOVED,
},
{
previousValue: { id: 9, name: "Item 9" },
currentValue: { id: 9, name: "Item 9" },
prevIndex: 8,
newIndex: 8,
indexDiff: 0,
status: LIST_STATUS.EQUAL,
},
{
previousValue: { id: 10, name: "Item 10" },
currentValue: { id: 10, name: "Item 10" },
prevIndex: 9,
newIndex: 6,
indexDiff: -3,
status: LIST_STATUS.MOVED,
},
],
[
{
previousValue: { id: 10, name: "Item 10" },
currentValue: { id: 10, name: "Item 10" },
prevIndex: 9,
newIndex: 6,
indexDiff: -3,
status: LIST_STATUS.MOVED,
},
],
];
let chunkCount = 0;
diff.on("data", (chunk) => {
// console.log("chunks received", chunk);
// console.log("expected chunk", expectedChunks[chunkCount]);
//expect(chunk).toStrictEqual(expectedChunks[chunkCount]);
chunkCount++;
});
diff.on("finish", () => {
expect(chunkCount).toBe(3);
done();
});
});
});
describe("streamListsDiff finish", () => {
it("emits 'finish' event if no prevList nor nextList is provided", (done) => {
const diff = streamListsDiff([], [], "id");
diff.on("finish", () => done());
});
it("emits 'finish' event when all the chunks have been processed", (done) => {
const prevList = [
{ id: 1, name: "Item 1" },
{ id: 2, name: "Item 2" },
];
const nextList = [
{ id: 2, name: "Item 2" },
{ id: 3, name: "Item 3" },
];
const diff = streamListsDiff(prevList, nextList, "id");
diff.on("finish", () => done());
});
});
describe("streamListsDiff error", () => {
test("emits 'error' event when prevList has invalid data", (done) => {
const prevList = [
{ id: 1, name: "Item 1" },
"hello",
{ id: 2, name: "Item 2" },
];
const nextList = [
{ id: 1, name: "Item 1" },
{ id: 2, name: "Item 2" },
];
// @ts-expect-error prevList is invalid by design for the test
const diff = streamListsDiff(prevList, nextList, "id");
diff.on("error", (err) => {
expect(err["message"]).toEqual(
`Your prevList must only contain valid objects. Found 'hello'`,
);
done();
});
});
test("emits 'error' event when nextList has invalid data", (done) => {
const prevList = [
{ id: 1, name: "Item 1" },
{ id: 2, name: "Item 2" },
];
const nextList = [
{ id: 1, name: "Item 1" },
"hello",
{ id: 2, name: "Item 2" },
];
// @ts-expect-error nextList is invalid by design for the test
const diff = streamListsDiff(prevList, nextList, "id");
diff.on("error", (err) => {
expect(err["message"]).toEqual(
`Your nextList must only contain valid objects. Found 'hello'`,
);
done();
});
});
test("emits 'error' event when all prevList ojects don't have the requested reference property", (done) => {
const prevList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }];
const nextList = [
{ id: 1, name: "Item 1" },
{ id: 2, name: "Item 2" },
];
const diff = streamListsDiff(prevList, nextList, "id");
diff.on("error", (err) => {
expect(err["message"]).toEqual(
`The reference property 'id' is not available in all the objects of your prevList.`,
);
done();
});
});
test("emits 'error' event when all nextList ojects don't have the requested reference property", (done) => {
const prevList = [
{ id: 1, name: "Item 1" },
{ id: 2, name: "Item 2" },
];
const nextList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }];
const diff = streamListsDiff(prevList, nextList, "id");
diff.on("error", (err) => {
expect(err["message"]).toEqual(
`The reference property 'id' is not available in all the objects of your nextList.`,
);
done();
});
});
test("emits 'error' event when the chunkSize option is negative", (done) => {
const prevList = [
{ id: 1, name: "Item 1" },
{ id: 2, name: "Item 2" },
];
const nextList = [{ id: 1, name: "Item 1" }, { name: "Item 2" }];
const diff = streamListsDiff(prevList, nextList, "id", { chunksSize: -3 });
diff.on("error", (err) => {
expect(err["message"]).toEqual(
"The chunk size can't be negative. You entered the value '-3'",
);
done();
});
});
});
Loading…
Cancel
Save