From fcbd3e325a62c4a2c83ff51f721fdfe26b7451d9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 5 Feb 2026 09:59:07 -0500 Subject: [PATCH] feat: allow use of createContext when instantiating components programmatically (#17575) * feat: allow use of createContext when instantiating components programmatically * docs --- .changeset/orange-ants-greet.md | 5 ++++ documentation/docs/06-runtime/02-context.md | 26 +++++++++++++++++++ packages/svelte/src/internal/client/render.js | 12 +++------ .../svelte/src/internal/server/renderer.js | 12 +++------ .../create-context-programmatic/Child.svelte | 7 +++++ .../create-context-programmatic/_config.js | 8 ++++++ .../create-context-programmatic/main.svelte | 20 ++++++++++++++ 7 files changed, 74 insertions(+), 16 deletions(-) create mode 100644 .changeset/orange-ants-greet.md create mode 100644 packages/svelte/tests/runtime-runes/samples/create-context-programmatic/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/create-context-programmatic/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/create-context-programmatic/main.svelte diff --git a/.changeset/orange-ants-greet.md b/.changeset/orange-ants-greet.md new file mode 100644 index 0000000000..6f7c684fee --- /dev/null +++ b/.changeset/orange-ants-greet.md @@ -0,0 +1,5 @@ +--- +'svelte': minor +--- + +feat: allow use of createContext when instantiating components programmatically diff --git a/documentation/docs/06-runtime/02-context.md b/documentation/docs/06-runtime/02-context.md index 0dfb996164..61b203f93e 100644 --- a/documentation/docs/06-runtime/02-context.md +++ b/documentation/docs/06-runtime/02-context.md @@ -97,6 +97,32 @@ import { createContext } from 'svelte'; export const [getUserContext, setUserContext] = createContext(); ``` +When writing [component tests](testing#Unit-and-component-tests-with-Vitest-Component-testing), it can be useful to create a wrapper component that sets the context in order to check the behaviour of a component that uses it. As of version 5.49, you can do this sort of thing: + +```js +import { mount, unmount } from 'svelte'; +import { expect, test } from 'vitest'; +import { setUserContext } from './context'; +import MyComponent from './MyComponent.svelte'; + +test('MyComponent', () => { + function Wrapper(...args) { + setUserContext({ name: 'Bob' }); + return MyComponent(...args); + } + + const component = mount(Wrapper, { + target: document.body + }); + + expect(document.body.innerHTML).toBe('

Hello Bob!

'); + + unmount(component); +}); +``` + +This approach also works with [`hydrate`](imperative-component-api#hydrate) and [`render`](imperative-component-api#render). + ## Replacing global state When you have state shared by many different components, you might be tempted to put it in its own module and just import it wherever it's needed: diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index c76f0b1ce7..c09f5fdd05 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -208,11 +208,9 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro pending: () => {} }, (anchor_node) => { - if (context) { - push({}); - var ctx = /** @type {ComponentContext} */ (component_context); - ctx.c = context; - } + push({}); + var ctx = /** @type {ComponentContext} */ (component_context); + if (context) ctx.c = context; if (events) { // We can't spread the object or else we'd lose the state proxy stuff, if it is one @@ -241,9 +239,7 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro } } - if (context) { - pop(); - } + pop(); } ); diff --git a/packages/svelte/src/internal/server/renderer.js b/packages/svelte/src/internal/server/renderer.js index 65f9bcae6a..49f4c1b7d2 100644 --- a/packages/svelte/src/internal/server/renderer.js +++ b/packages/svelte/src/internal/server/renderer.js @@ -617,18 +617,14 @@ export class Renderer { renderer.push(BLOCK_OPEN); - if (options.context) { - push(); - /** @type {SSRContext} */ (ssr_context).c = options.context; - /** @type {SSRContext} */ (ssr_context).r = renderer; - } + push(); + if (options.context) /** @type {SSRContext} */ (ssr_context).c = options.context; + /** @type {SSRContext} */ (ssr_context).r = renderer; // @ts-expect-error component(renderer, options.props ?? {}); - if (options.context) { - pop(); - } + pop(); renderer.push(BLOCK_CLOSE); diff --git a/packages/svelte/tests/runtime-runes/samples/create-context-programmatic/Child.svelte b/packages/svelte/tests/runtime-runes/samples/create-context-programmatic/Child.svelte new file mode 100644 index 0000000000..3e39d5043e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/create-context-programmatic/Child.svelte @@ -0,0 +1,7 @@ + + +

{message}

diff --git a/packages/svelte/tests/runtime-runes/samples/create-context-programmatic/_config.js b/packages/svelte/tests/runtime-runes/samples/create-context-programmatic/_config.js new file mode 100644 index 0000000000..f4374c8759 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/create-context-programmatic/_config.js @@ -0,0 +1,8 @@ +import { test } from '../../test'; + +export default test({ + ssrHtml: `
`, + html: `

hello

`, + + test() {} +}); diff --git a/packages/svelte/tests/runtime-runes/samples/create-context-programmatic/main.svelte b/packages/svelte/tests/runtime-runes/samples/create-context-programmatic/main.svelte new file mode 100644 index 0000000000..3c42dbf180 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/create-context-programmatic/main.svelte @@ -0,0 +1,20 @@ + + +
{ + mount(Wrapper(Child), { target }); +}}>