From 8179c6a6438f9943cf1b3fe268cfbe18810546a0 Mon Sep 17 00:00:00 2001 From: Elliott Johnson Date: Fri, 31 Oct 2025 13:56:31 -0600 Subject: [PATCH] tests --- packages/svelte/src/client/index.js | 5 ++- .../svelte/src/internal/client/hydratable.js | 15 +++++++ .../svelte/src/internal/server/renderer.js | 2 +- .../src/internal/server/renderer.test.ts | 39 ++++++++++++++++++- 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/packages/svelte/src/client/index.js b/packages/svelte/src/client/index.js index e92140b08e..1cd028d123 100644 --- a/packages/svelte/src/client/index.js +++ b/packages/svelte/src/client/index.js @@ -1 +1,4 @@ -export { get_hydratable_value as getHydratableValue } from '../internal/client/hydratable.js'; +export { + get_hydratable_value as getHydratableValue, + has_hydratable_value as hasHydratableValue +} from '../internal/client/hydratable.js'; diff --git a/packages/svelte/src/internal/client/hydratable.js b/packages/svelte/src/internal/client/hydratable.js index f8c095eb03..0c61d4f504 100644 --- a/packages/svelte/src/internal/client/hydratable.js +++ b/packages/svelte/src/internal/client/hydratable.js @@ -49,6 +49,21 @@ export function get_hydratable_value(key, options = {}) { return parse(val, options.parse); } +/** + * @param {string} key + * @returns {boolean} + */ +export function has_hydratable_value(key) { + if (!hydrating) { + return false; + } + var store = window.__svelte?.h; + if (store === undefined) { + throw new Error('TODO this should be impossible?'); + } + return store.has(key); +} + /** * @template T * @param {string} val diff --git a/packages/svelte/src/internal/server/renderer.js b/packages/svelte/src/internal/server/renderer.js index a9540d86ce..7d8dd4eb78 100644 --- a/packages/svelte/src/internal/server/renderer.js +++ b/packages/svelte/src/internal/server/renderer.js @@ -665,7 +665,7 @@ export class SSRState { } } -class MemoizedUneval { +export class MemoizedUneval { /** @type {Map} */ #cache = new Map(); diff --git a/packages/svelte/src/internal/server/renderer.test.ts b/packages/svelte/src/internal/server/renderer.test.ts index 8e9a377a5b..a2a979aeb4 100644 --- a/packages/svelte/src/internal/server/renderer.test.ts +++ b/packages/svelte/src/internal/server/renderer.test.ts @@ -1,7 +1,8 @@ import { afterAll, beforeAll, describe, expect, test } from 'vitest'; -import { Renderer, SSRState } from './renderer.js'; +import { MemoizedUneval, Renderer, SSRState } from './renderer.js'; import type { Component } from 'svelte'; import { disable_async_mode_flag, enable_async_mode_flag } from '../flags/index.js'; +import { uneval } from 'devalue'; test('collects synchronous body content by default', () => { const component = (renderer: Renderer) => { @@ -355,3 +356,39 @@ describe('async', () => { expect(destroyed).toEqual(['c', 'e', 'a', 'b', 'b*', 'd']); }); }); + +describe('MemoizedDevalue', () => { + test.each([ + 1, + 'general kenobi', + { foo: 'bar' }, + [1, 2], + null, + undefined, + new Map([[1, '2']]) + ] as const)('has same behavior as unmemoized devalue for %s', (input) => { + expect(new MemoizedUneval().uneval(input)).toBe(uneval(input)); + }); + + test('caches results', () => { + const memoized = new MemoizedUneval(); + let calls = 0; + + const input = { + get only_once() { + calls++; + return 42; + } + }; + + const first = memoized.uneval(input); + const max_calls = calls; + const second = memoized.uneval(input); + memoized.uneval(input); + + expect(first).toBe(second); + // for reasons I don't quite comprehend, it does get called twice, but both calls happen in the first + // serialization, and don't increase afterwards + expect(calls).toBe(max_calls); + }); +});