feat(dev): add deepclone util function (@miodec) (#5882)

!nuf
This commit is contained in:
Jack 2024-09-13 13:24:56 +02:00 committed by GitHub
parent 955eeae2a7
commit 70842599a9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 89 additions and 16 deletions

View file

@ -1,4 +1,4 @@
import { isObject } from "../../src/ts/utils/misc";
import { deepClone, isObject } from "../../src/ts/utils/misc";
import {
getLanguageDisplayString,
removeLanguageSize,
@ -118,4 +118,55 @@ describe("misc.ts", () => {
});
});
});
describe("deepClone", () => {
it("should correctly clone objects", () => {
const tests = [
{
input: {},
expected: {},
},
{
input: { a: 1 },
expected: { a: 1 },
},
{
input: { a: { b: 2 } },
expected: { a: { b: 2 } },
},
{
input: { a: { b: 2 }, c: [1, 2, 3] },
expected: { a: { b: 2 }, c: [1, 2, 3] },
},
{
input: [],
expected: [],
},
{
input: [1, 2, 3],
expected: [1, 2, 3],
},
{
input: "string",
expected: "string",
},
{
input: 1,
expected: 1,
},
{
input: null,
expected: null,
},
{
input: undefined,
expected: undefined,
},
];
tests.forEach((test) => {
const result = deepClone(test.input);
expect(result).toStrictEqual(test.expected);
});
});
});
});

View file

@ -2,6 +2,7 @@ import {
Config,
CustomThemeColors,
} from "@monkeytype/contracts/schemas/configs";
import { deepClone } from "../utils/misc";
const obj = {
theme: "serika_dark",
@ -102,4 +103,4 @@ const obj = {
maxLineWidth: 0,
} as Config;
export default JSON.parse(JSON.stringify(obj)) as Config;
export default deepClone(obj);

View file

@ -1,4 +1,5 @@
import { ResultFilters } from "@monkeytype/contracts/schemas/users";
import { deepClone } from "../utils/misc";
const object: ResultFilters = {
_id: "default",
@ -63,4 +64,4 @@ const object: ResultFilters = {
},
};
export default JSON.parse(JSON.stringify(object)) as ResultFilters;
export default deepClone(object);

View file

@ -166,16 +166,12 @@ export async function setFilterPreset(id: string): Promise<void> {
).addClass("active");
}
function deepCopyFilter(filter: ResultFilters): ResultFilters {
return JSON.parse(JSON.stringify(filter)) as ResultFilters;
}
function addFilterPresetToSnapshot(filter: ResultFilters): void {
const snapshot = DB.getSnapshot();
if (!snapshot) return;
DB.setSnapshot({
...snapshot,
filterPresets: [...snapshot.filterPresets, deepCopyFilter(filter)],
filterPresets: [...snapshot.filterPresets, Misc.deepClone(filter)],
});
}
@ -963,7 +959,7 @@ $(".group.presetFilterButtons .filterBtns").on(
);
function verifyResultFiltersStructure(filterIn: ResultFilters): ResultFilters {
const filter = deepCopyFilter(filterIn);
const filter = Misc.deepClone(filterIn);
Object.entries(defaultResultFilters).forEach((entry) => {
const key = entry[0] as ResultFiltersGroup;
const value = entry[1];

View file

@ -839,7 +839,7 @@ export async function update(
dontSave: boolean
): Promise<void> {
resultAnnotation = [];
result = Object.assign({}, res);
result = Misc.deepClone(res);
hideCrown();
$("#resultWordsHistory .words").empty();
$("#result #resultWordsHistory").addClass("hidden");

View file

@ -74,7 +74,7 @@ export function clearNotSignedInResult(): void {
notSignedInLastResult = null;
}
export function setNotSignedInUid(uid: string): void {
export function setNotSignedInUidAndHash(uid: string): void {
if (notSignedInLastResult === null) return;
notSignedInLastResult.uid = uid;
//@ts-expect-error
@ -897,7 +897,7 @@ export async function finish(difficultyFailed = false): Promise<void> {
dontSave = true;
}
const completedEvent = JSON.parse(JSON.stringify(ce)) as CompletedEvent;
const completedEvent = Misc.deepClone(ce) as CompletedEvent;
///////// completed event ready
@ -1054,9 +1054,7 @@ export async function finish(difficultyFailed = false): Promise<void> {
$("#result .stats .dailyLeaderboard").addClass("hidden");
TestStats.setLastResult(
JSON.parse(JSON.stringify(completedEvent)) as CompletedEvent
);
TestStats.setLastResult(Misc.deepClone(completedEvent));
if (!ConnectionState.get()) {
ConnectionState.showOfflineBanner();

View file

@ -705,4 +705,30 @@ export function parseJsonWithSchema<T>(input: string, schema: ZodSchema<T>): T {
}
}
export function deepClone<T>(obj: T[]): T[];
export function deepClone<T extends object>(obj: T): T;
export function deepClone<T>(obj: T): T;
export function deepClone<T>(obj: T | T[]): T | T[] {
// Check if the value is a primitive (not an object or array)
if (obj === null || typeof obj !== "object") {
return obj;
}
// Handle arrays
if (Array.isArray(obj)) {
return obj.map((item) => deepClone(item));
}
// Handle objects
const clonedObj = {} as { [K in keyof T]: T[K] };
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
clonedObj[key] = deepClone((obj as { [K in keyof T]: T[K] })[key]);
}
}
return clonedObj;
}
// DO NOT ALTER GLOBAL OBJECTSONSTRUCTOR, IT WILL BREAK RESULT HASHES

View file

@ -6,7 +6,7 @@ import * as TestLogic from "../test/test-logic";
export async function syncNotSignedInLastResult(uid: string): Promise<void> {
const notSignedInLastResult = TestLogic.notSignedInLastResult;
if (notSignedInLastResult === null) return;
TestLogic.setNotSignedInUid(uid);
TestLogic.setNotSignedInUidAndHash(uid);
const response = await Ape.results.add({
body: { result: notSignedInLastResult },