mirror of https://github.com/sveltejs/svelte
parent
bf0d1efb00
commit
9940630210
@ -0,0 +1,69 @@
|
||||
import { STATE_SYMBOL } from './constants.js';
|
||||
import {
|
||||
define_property,
|
||||
get_descriptors,
|
||||
get_prototype_of,
|
||||
is_array,
|
||||
object_prototype
|
||||
} from './utils.js';
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {T} value
|
||||
* @returns {T}
|
||||
*/
|
||||
export function snapshot(value) {
|
||||
return /** @type {T} */ (
|
||||
clone(/** @type {import('#client').ProxyStateObject} */ (value), new Map())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {import('#client').ProxyStateObject} T
|
||||
* @param {T} value
|
||||
* @param {Map<T, Record<string | symbol, any>>} cloned
|
||||
* @returns {Record<string | symbol, any>}
|
||||
*/
|
||||
function clone(value, cloned) {
|
||||
if (typeof value === 'object' && value != null) {
|
||||
const unwrapped = cloned.get(value);
|
||||
if (unwrapped !== undefined) {
|
||||
return unwrapped;
|
||||
}
|
||||
|
||||
if (is_array(value)) {
|
||||
/** @type {Record<string | symbol, any>} */
|
||||
const array = [];
|
||||
cloned.set(value, array);
|
||||
for (const element of value) {
|
||||
array.push(clone(element, cloned));
|
||||
}
|
||||
return array;
|
||||
} else if (get_prototype_of(value) === object_prototype) {
|
||||
/** @type {Record<string | symbol, any>} */
|
||||
const obj = {};
|
||||
const keys = Reflect.ownKeys(value);
|
||||
const descriptors = get_descriptors(value);
|
||||
cloned.set(value, obj);
|
||||
|
||||
for (const key of keys) {
|
||||
if (key === STATE_SYMBOL) continue;
|
||||
if (descriptors[key].get) {
|
||||
define_property(obj, key, descriptors[key]);
|
||||
} else {
|
||||
/** @type {T} */
|
||||
const property = value[key];
|
||||
obj[key] = clone(property, cloned);
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (typeof value.toJSON === 'function') {
|
||||
return clone(value.toJSON(), cloned);
|
||||
}
|
||||
}
|
||||
|
||||
return structuredClone(value);
|
||||
}
|
||||
@ -0,0 +1,102 @@
|
||||
import { snapshot } from './clone';
|
||||
import { assert, test } from 'vitest';
|
||||
import { proxy } from './proxy';
|
||||
|
||||
test('primitive', () => {
|
||||
assert.equal(42, snapshot(42));
|
||||
});
|
||||
|
||||
test('array', () => {
|
||||
const array = [1, 2, 3];
|
||||
const copy = snapshot(array);
|
||||
|
||||
assert.deepEqual(copy, array);
|
||||
assert.notEqual(copy, array);
|
||||
});
|
||||
|
||||
test('object', () => {
|
||||
const object = { a: 1, b: 2, c: 3 };
|
||||
const copy = snapshot(object);
|
||||
|
||||
assert.deepEqual(copy, object);
|
||||
assert.notEqual(copy, object);
|
||||
});
|
||||
|
||||
test('proxied state', () => {
|
||||
const object = proxy({
|
||||
a: {
|
||||
b: {
|
||||
c: 1
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const copy = snapshot(object);
|
||||
|
||||
assert.deepEqual(copy, object);
|
||||
assert.notEqual(copy, object);
|
||||
|
||||
object.a.b.c = 2;
|
||||
assert.equal(copy.a.b.c, 1);
|
||||
});
|
||||
|
||||
test('cycles', () => {
|
||||
const object: { self?: any } = {};
|
||||
object.self = object;
|
||||
const copy = snapshot(object);
|
||||
|
||||
assert.equal(copy.self, copy);
|
||||
});
|
||||
|
||||
test('class with state field', () => {
|
||||
class Foo {
|
||||
x = 1;
|
||||
#y = 2;
|
||||
|
||||
get y() {
|
||||
return this.#y;
|
||||
}
|
||||
}
|
||||
|
||||
const copy = snapshot(new Foo());
|
||||
|
||||
assert.deepEqual(copy, { x: 1 });
|
||||
});
|
||||
|
||||
test('class with toJSON', () => {
|
||||
class Foo {
|
||||
x = 1;
|
||||
#y = 2;
|
||||
|
||||
get y() {
|
||||
return this.#y;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
x: this.x,
|
||||
y: this.y
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const copy = snapshot(new Foo());
|
||||
|
||||
assert.deepEqual(copy, { x: 1, y: 2 });
|
||||
});
|
||||
|
||||
test('reactive class', () => {
|
||||
class SvelteMap<T, U> extends Map<T, U> {
|
||||
constructor(init?: Iterable<[T, U]>) {
|
||||
super(init);
|
||||
}
|
||||
}
|
||||
|
||||
const map = new SvelteMap([[1, 2]]);
|
||||
const copy = snapshot(map);
|
||||
|
||||
assert.ok(copy instanceof Map);
|
||||
assert.notOk(copy instanceof SvelteMap);
|
||||
|
||||
assert.equal(copy.get(1), 2);
|
||||
});
|
||||
Loading…
Reference in new issue