@bleedingdev/modern-js-plugin-tanstack 3.2.0-ultramodern.12 → 3.2.0-ultramodern.121

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/dist/cjs/cli/index.js +89 -31
  2. package/dist/cjs/cli/routeSplitting.js +55 -0
  3. package/dist/cjs/cli/tanstackTypes.js +172 -170
  4. package/dist/cjs/cli.js +12 -8
  5. package/dist/cjs/runtime/basepathRewrite.js +12 -8
  6. package/dist/cjs/runtime/dataMutation.js +9 -5
  7. package/dist/cjs/runtime/hooks.js +20 -19
  8. package/dist/cjs/runtime/hydrationBoundary.js +48 -0
  9. package/dist/cjs/runtime/index.js +79 -35
  10. package/dist/cjs/runtime/lifecycle.js +21 -91
  11. package/dist/cjs/runtime/loaderBridge.js +173 -0
  12. package/dist/cjs/runtime/outlet.js +58 -0
  13. package/dist/cjs/runtime/plugin.js +195 -114
  14. package/dist/cjs/runtime/plugin.node.js +45 -45
  15. package/dist/cjs/runtime/plugin.worker.js +53 -0
  16. package/dist/cjs/runtime/pluginCore.js +55 -0
  17. package/dist/cjs/runtime/prefetchLink.js +10 -6
  18. package/dist/cjs/runtime/register.js +56 -0
  19. package/dist/cjs/runtime/routeTree.js +74 -207
  20. package/dist/cjs/runtime/router.js +41 -0
  21. package/dist/cjs/runtime/rsc/ClientSlot.js +9 -5
  22. package/dist/cjs/runtime/rsc/CompositeComponent.js +9 -5
  23. package/dist/cjs/runtime/rsc/ReplayableStream.js +14 -9
  24. package/dist/cjs/runtime/rsc/RscNodeRenderer.js +9 -5
  25. package/dist/cjs/runtime/rsc/SlotContext.js +9 -5
  26. package/dist/cjs/runtime/rsc/client.js +9 -5
  27. package/dist/cjs/runtime/rsc/createRscProxy.js +9 -5
  28. package/dist/cjs/runtime/rsc/index.js +9 -5
  29. package/dist/cjs/runtime/rsc/payloadRouter.js +44 -6
  30. package/dist/cjs/runtime/rsc/server.js +9 -5
  31. package/dist/cjs/runtime/rsc/slotUsageSanitizer.js +9 -5
  32. package/dist/cjs/runtime/rsc/symbols.js +20 -15
  33. package/dist/cjs/runtime/state.js +45 -0
  34. package/dist/cjs/runtime/types.js +31 -1
  35. package/dist/cjs/runtime/utils.js +9 -10
  36. package/dist/cjs/runtime.js +9 -5
  37. package/dist/esm/cli/index.mjs +75 -27
  38. package/dist/esm/cli/routeSplitting.mjs +14 -0
  39. package/dist/esm/cli/tanstackTypes.mjs +158 -160
  40. package/dist/esm/runtime/hooks.mjs +1 -8
  41. package/dist/esm/runtime/hydrationBoundary.mjs +10 -0
  42. package/dist/esm/runtime/index.mjs +5 -2
  43. package/dist/esm/runtime/lifecycle.mjs +1 -82
  44. package/dist/esm/runtime/loaderBridge.mjs +114 -0
  45. package/dist/esm/runtime/outlet.mjs +17 -0
  46. package/dist/esm/runtime/plugin.mjs +191 -114
  47. package/dist/esm/runtime/plugin.node.mjs +40 -44
  48. package/dist/esm/runtime/plugin.worker.mjs +1 -0
  49. package/dist/esm/runtime/pluginCore.mjs +14 -0
  50. package/dist/esm/runtime/prefetchLink.mjs +1 -1
  51. package/dist/esm/runtime/register.mjs +18 -0
  52. package/dist/esm/runtime/routeTree.mjs +59 -193
  53. package/dist/esm/runtime/router.mjs +2 -0
  54. package/dist/esm/runtime/rsc/payloadRouter.mjs +35 -1
  55. package/dist/esm/runtime/state.mjs +7 -0
  56. package/dist/esm/runtime/types.mjs +7 -0
  57. package/dist/esm/runtime/utils.mjs +0 -5
  58. package/dist/esm-node/cli/index.mjs +75 -27
  59. package/dist/esm-node/cli/routeSplitting.mjs +15 -0
  60. package/dist/esm-node/cli/tanstackTypes.mjs +158 -160
  61. package/dist/esm-node/runtime/hooks.mjs +1 -8
  62. package/dist/esm-node/runtime/hydrationBoundary.mjs +11 -0
  63. package/dist/esm-node/runtime/index.mjs +5 -2
  64. package/dist/esm-node/runtime/lifecycle.mjs +1 -82
  65. package/dist/esm-node/runtime/loaderBridge.mjs +115 -0
  66. package/dist/esm-node/runtime/outlet.mjs +18 -0
  67. package/dist/esm-node/runtime/plugin.mjs +191 -114
  68. package/dist/esm-node/runtime/plugin.node.mjs +40 -44
  69. package/dist/esm-node/runtime/plugin.worker.mjs +2 -0
  70. package/dist/esm-node/runtime/pluginCore.mjs +15 -0
  71. package/dist/esm-node/runtime/prefetchLink.mjs +1 -1
  72. package/dist/esm-node/runtime/register.mjs +19 -0
  73. package/dist/esm-node/runtime/routeTree.mjs +59 -193
  74. package/dist/esm-node/runtime/router.mjs +3 -0
  75. package/dist/esm-node/runtime/rsc/payloadRouter.mjs +35 -1
  76. package/dist/esm-node/runtime/state.mjs +8 -0
  77. package/dist/esm-node/runtime/types.mjs +7 -0
  78. package/dist/esm-node/runtime/utils.mjs +0 -5
  79. package/dist/types/cli/index.d.ts +14 -1
  80. package/dist/types/cli/routeSplitting.d.ts +20 -0
  81. package/dist/types/cli/tanstackTypes.d.ts +21 -1
  82. package/dist/types/runtime/hooks.d.ts +8 -33
  83. package/dist/types/runtime/hydrationBoundary.d.ts +2 -0
  84. package/dist/types/runtime/index.d.ts +8 -3
  85. package/dist/types/runtime/lifecycle.d.ts +7 -22
  86. package/dist/types/runtime/loaderBridge.d.ts +48 -0
  87. package/dist/types/runtime/outlet.d.ts +2 -0
  88. package/dist/types/runtime/plugin.d.ts +2 -15
  89. package/dist/types/runtime/plugin.node.d.ts +2 -15
  90. package/dist/types/runtime/plugin.worker.d.ts +1 -0
  91. package/dist/types/runtime/pluginCore.d.ts +21 -0
  92. package/dist/types/runtime/register.d.ts +9 -0
  93. package/dist/types/runtime/routeTree.d.ts +0 -2
  94. package/dist/types/runtime/router.d.ts +14 -0
  95. package/dist/types/runtime/state.d.ts +16 -0
  96. package/dist/types/runtime/types.d.ts +14 -53
  97. package/package.json +42 -40
  98. package/rstest.config.mts +6 -0
  99. package/src/cli/index.ts +162 -23
  100. package/src/cli/routeSplitting.ts +43 -0
  101. package/src/cli/tanstackTypes.ts +331 -187
  102. package/src/runtime/hooks.ts +10 -27
  103. package/src/runtime/hydrationBoundary.tsx +12 -0
  104. package/src/runtime/index.tsx +17 -7
  105. package/src/runtime/lifecycle.ts +16 -151
  106. package/src/runtime/loaderBridge.ts +257 -0
  107. package/src/runtime/outlet.tsx +48 -0
  108. package/src/runtime/plugin.node.tsx +72 -85
  109. package/src/runtime/plugin.tsx +361 -206
  110. package/src/runtime/plugin.worker.tsx +4 -0
  111. package/src/runtime/pluginCore.ts +48 -0
  112. package/src/runtime/prefetchLink.tsx +1 -1
  113. package/src/runtime/register.ts +58 -0
  114. package/src/runtime/routeTree.ts +163 -354
  115. package/src/runtime/router.ts +15 -0
  116. package/src/runtime/rsc/payloadRouter.ts +45 -2
  117. package/src/runtime/ssr-shim.d.ts +1 -3
  118. package/src/runtime/state.ts +29 -0
  119. package/src/runtime/types.ts +32 -66
  120. package/src/runtime/utils.tsx +3 -6
  121. package/tests/router/cli.test.ts +586 -5
  122. package/tests/router/fastDefaults.test.ts +25 -0
  123. package/tests/router/hooks.test.ts +26 -0
  124. package/tests/router/hydrationBoundary.test.tsx +23 -0
  125. package/tests/router/loaderBridge.test.ts +211 -0
  126. package/tests/router/packageSurface.test.ts +24 -0
  127. package/tests/router/prefetchLink.test.tsx +43 -7
  128. package/tests/router/register.test.ts +46 -0
  129. package/tests/router/routeTree.test.ts +381 -81
  130. package/tests/router/rsc.test.tsx +70 -0
  131. package/tests/router/tanstackTypes.test.ts +573 -1
  132. package/dist/cjs/runtime/DefaultNotFound.js +0 -47
  133. package/dist/esm/runtime/DefaultNotFound.mjs +0 -13
  134. package/dist/esm-node/runtime/DefaultNotFound.mjs +0 -14
  135. package/dist/types/runtime/DefaultNotFound.d.ts +0 -2
  136. package/src/runtime/DefaultNotFound.tsx +0 -15
@@ -0,0 +1,23 @@
1
+ import { isValidElement, Suspense } from 'react';
2
+ import { wrapTanstackSsrHydrationBoundary } from '../../src/runtime/hydrationBoundary';
3
+
4
+ describe('tanstack SSR hydration boundary', () => {
5
+ it('wraps SSR hydration content in a Suspense boundary', () => {
6
+ const routerContent = <main data-testid="router" />;
7
+
8
+ const wrapped = wrapTanstackSsrHydrationBoundary(routerContent, true);
9
+
10
+ expect(isValidElement(wrapped)).toBe(true);
11
+ expect(wrapped.type).toBe(Suspense);
12
+ expect(wrapped.props.fallback).toBe(null);
13
+ expect(wrapped.props.children).toBe(routerContent);
14
+ });
15
+
16
+ it('keeps non-SSR router content unwrapped', () => {
17
+ const routerContent = <main data-testid="router" />;
18
+
19
+ expect(wrapTanstackSsrHydrationBoundary(routerContent, false)).toBe(
20
+ routerContent,
21
+ );
22
+ });
23
+ });
@@ -0,0 +1,211 @@
1
+ import { isNotFound, isRedirect } from '@tanstack/react-router';
2
+ import {
3
+ createRouteStaticData,
4
+ isAbsoluteUrl,
5
+ mapSplatParamsForModernLoader,
6
+ modernLoaderToTanstack,
7
+ throwTanstackRedirect,
8
+ } from '../../src/runtime/loaderBridge';
9
+
10
+ type RedirectLike = {
11
+ options?: {
12
+ href?: string;
13
+ to?: string;
14
+ };
15
+ };
16
+
17
+ function catchThrown(fn: () => unknown): unknown {
18
+ try {
19
+ fn();
20
+ } catch (err) {
21
+ return err;
22
+ }
23
+ throw new Error('expected the function to throw');
24
+ }
25
+
26
+ describe('throwTanstackRedirect', () => {
27
+ test('absolute URLs redirect via href (external), not via to', () => {
28
+ // The old inline codegen handler threw `redirect({ href })` INSIDE a
29
+ // try block whose catch replaced it with `redirect({ to: absoluteUrl })`,
30
+ // making TanStack treat the absolute URL as an internal path.
31
+ const thrown = catchThrown(() =>
32
+ throwTanstackRedirect('https://example.com/external'),
33
+ ) as RedirectLike;
34
+
35
+ expect(isRedirect(thrown)).toBe(true);
36
+ expect(thrown.options?.href).toBe('https://example.com/external');
37
+ expect(thrown.options?.to).toBeUndefined();
38
+ });
39
+
40
+ test('relative paths redirect via to so the basepath rewrite applies', () => {
41
+ const thrown = catchThrown(() =>
42
+ throwTanstackRedirect('/dashboard'),
43
+ ) as RedirectLike;
44
+
45
+ expect(isRedirect(thrown)).toBe(true);
46
+ expect(thrown.options?.to).toBe('/dashboard');
47
+ expect(thrown.options?.href).toBeUndefined();
48
+ });
49
+
50
+ test('empty location falls back to /', () => {
51
+ const thrown = catchThrown(() => throwTanstackRedirect('')) as RedirectLike;
52
+ expect(thrown.options?.to).toBe('/');
53
+ });
54
+ });
55
+
56
+ describe('isAbsoluteUrl', () => {
57
+ test('detects absolute and relative URLs', () => {
58
+ expect(isAbsoluteUrl('https://example.com/a')).toBe(true);
59
+ expect(isAbsoluteUrl('mailto:x@example.com')).toBe(true);
60
+ expect(isAbsoluteUrl('/internal/path')).toBe(false);
61
+ expect(isAbsoluteUrl('relative')).toBe(false);
62
+ });
63
+ });
64
+
65
+ describe('mapSplatParamsForModernLoader', () => {
66
+ test('maps TanStack _splat to React Router * only for splat routes', () => {
67
+ expect(
68
+ mapSplatParamsForModernLoader({ _splat: 'a/b', id: '1' }, true),
69
+ ).toEqual({ '*': 'a/b', id: '1' });
70
+ expect(
71
+ mapSplatParamsForModernLoader({ _splat: 'a/b', id: '1' }, false),
72
+ ).toEqual({ _splat: 'a/b', id: '1' });
73
+ expect(mapSplatParamsForModernLoader({ id: '1' }, true)).toEqual({
74
+ id: '1',
75
+ });
76
+ });
77
+ });
78
+
79
+ describe('createRouteStaticData', () => {
80
+ test('drops empty fields', () => {
81
+ const loader = () => null;
82
+ expect(createRouteStaticData({})).toEqual({});
83
+ expect(createRouteStaticData({ modernRouteId: '' })).toEqual({});
84
+ expect(
85
+ createRouteStaticData({
86
+ modernRouteId: 'page',
87
+ modernRouteLoader: loader,
88
+ }),
89
+ ).toEqual({ modernRouteId: 'page', modernRouteLoader: loader });
90
+ });
91
+ });
92
+
93
+ describe('modernLoaderToTanstack', () => {
94
+ const baseCtx = {
95
+ location: { href: 'http://localhost/products/1' },
96
+ params: { id: '1' },
97
+ };
98
+
99
+ test('passes request/params/context through to the modern loader', async () => {
100
+ const seen: { request?: Request; params?: unknown; context?: unknown } = {};
101
+ const loader = modernLoaderToTanstack({ hasSplat: false }, (args: any) => {
102
+ seen.request = args.request;
103
+ seen.params = args.params;
104
+ seen.context = args.context;
105
+ return { ok: true };
106
+ });
107
+
108
+ await expect(
109
+ loader({
110
+ ...baseCtx,
111
+ context: { requestContext: { user: 'u1' } },
112
+ }),
113
+ ).resolves.toEqual({ ok: true });
114
+ expect(seen.request).toBeInstanceOf(Request);
115
+ expect(seen.request?.url).toBe('http://localhost/products/1');
116
+ expect(seen.params).toEqual({ id: '1' });
117
+ expect(seen.context).toEqual({ user: 'u1' });
118
+ });
119
+
120
+ test('translates an absolute-URL redirect Response into redirect({ href })', async () => {
121
+ const loader = modernLoaderToTanstack({ hasSplat: false }, () =>
122
+ Response.redirect('https://example.com/away', 302),
123
+ );
124
+
125
+ const thrown = (await loader(baseCtx).then(
126
+ () => {
127
+ throw new Error('expected redirect');
128
+ },
129
+ (err: unknown) => err,
130
+ )) as RedirectLike;
131
+
132
+ expect(isRedirect(thrown)).toBe(true);
133
+ expect(thrown.options?.href).toBe('https://example.com/away');
134
+ expect(thrown.options?.to).toBeUndefined();
135
+ });
136
+
137
+ test('translates a relative redirect Response into redirect({ to })', async () => {
138
+ const loader = modernLoaderToTanstack(
139
+ { hasSplat: false },
140
+ () =>
141
+ new Response(null, { status: 302, headers: { Location: '/login' } }),
142
+ );
143
+
144
+ const thrown = (await loader(baseCtx).then(
145
+ () => {
146
+ throw new Error('expected redirect');
147
+ },
148
+ (err: unknown) => err,
149
+ )) as RedirectLike;
150
+
151
+ expect(isRedirect(thrown)).toBe(true);
152
+ expect(thrown.options?.to).toBe('/login');
153
+ });
154
+
155
+ test('translates a 404 Response into notFound()', async () => {
156
+ const loader = modernLoaderToTanstack(
157
+ { hasSplat: false },
158
+ () => new Response(null, { status: 404 }),
159
+ );
160
+
161
+ const thrown = await loader(baseCtx).then(
162
+ () => {
163
+ throw new Error('expected notFound');
164
+ },
165
+ (err: unknown) => err,
166
+ );
167
+
168
+ expect(isNotFound(thrown)).toBe(true);
169
+ });
170
+
171
+ test('preserves returned non-404 error Responses as loader results', async () => {
172
+ const response = new Response('loader exploded', { status: 500 });
173
+ const loader = modernLoaderToTanstack({ hasSplat: false }, () => response);
174
+
175
+ await expect(loader(baseCtx)).resolves.toBe(response);
176
+ });
177
+
178
+ test('translates redirect Responses thrown synchronously by the loader', () => {
179
+ const loader = modernLoaderToTanstack({ hasSplat: false }, () => {
180
+ throw new Response(null, {
181
+ status: 301,
182
+ headers: { Location: 'https://example.com/moved' },
183
+ });
184
+ });
185
+
186
+ // A synchronous loader throw surfaces synchronously (TanStack handles
187
+ // thrown redirects from the loader call itself).
188
+ const thrown = catchThrown(() => loader(baseCtx)) as RedirectLike;
189
+
190
+ expect(isRedirect(thrown)).toBe(true);
191
+ expect(thrown.options?.href).toBe('https://example.com/moved');
192
+ });
193
+
194
+ test('re-throws TanStack redirects thrown by the loader untouched', async () => {
195
+ const loader = modernLoaderToTanstack({ hasSplat: false }, async () => {
196
+ throwTanstackRedirect('/inner');
197
+ });
198
+
199
+ const thrown = (await loader(baseCtx).then(
200
+ () => {
201
+ throw new Error('expected redirect');
202
+ },
203
+ (err: unknown) => err,
204
+ )) as RedirectLike;
205
+
206
+ // The bridge must not re-translate its own redirect (a Response without
207
+ // a Location header) — that used to collapse internal targets to '/'.
208
+ expect(isRedirect(thrown)).toBe(true);
209
+ expect(thrown.options?.to).toBe('/inner');
210
+ });
211
+ });
@@ -0,0 +1,24 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ describe('tanstack package public surface', () => {
5
+ test('package manifest exposes the runtime subpath used by app fixtures', () => {
6
+ const packageJson = JSON.parse(
7
+ readFileSync(path.join(__dirname, '../../package.json'), 'utf-8'),
8
+ ) as {
9
+ exports: Record<string, unknown>;
10
+ typesVersions?: Record<string, Record<string, string[]>>;
11
+ };
12
+
13
+ expect(packageJson.exports['./runtime']).toEqual({
14
+ types: './dist/types/runtime/index.d.ts',
15
+ node: {
16
+ module: './dist/esm/runtime/index.mjs',
17
+ },
18
+ default: './dist/esm/runtime/index.mjs',
19
+ });
20
+ expect(packageJson.typesVersions?.['*']?.runtime).toEqual([
21
+ './dist/types/runtime/index.d.ts',
22
+ ]);
23
+ });
24
+ });
@@ -1,6 +1,6 @@
1
1
  import { render } from '@testing-library/react';
2
2
  import React from 'react';
3
- import { Link } from '../../src/runtime/prefetchLink';
3
+ import { Link, NavLink } from '../../src/runtime/prefetchLink';
4
4
 
5
5
  type MockLinkProps = {
6
6
  children?: React.ReactNode;
@@ -21,23 +21,59 @@ describe('tanstack prefetch link adapter', () => {
21
21
  capturedPreloads = [];
22
22
  });
23
23
 
24
- it('maps viewport prefetch to TanStack preload', () => {
24
+ it('defaults TanStack preload to viewport', () => {
25
+ render(<Link to="/settings">Settings</Link>);
26
+
27
+ expect(capturedPreloads).toEqual(['viewport']);
28
+ });
29
+
30
+ it('preserves explicit preload', () => {
25
31
  render(
26
- <Link to="/settings" prefetch="viewport">
32
+ <Link to="/settings" prefetch="render" preload="intent">
27
33
  Settings
28
34
  </Link>,
29
35
  );
30
36
 
31
- expect(capturedPreloads).toEqual(['viewport']);
37
+ expect(capturedPreloads).toEqual(['intent']);
32
38
  });
33
39
 
34
- it('does not override explicit preload', () => {
40
+ it('preserves explicit disabled preload', () => {
35
41
  render(
36
- <Link to="/settings" prefetch="viewport" preload="intent">
42
+ <Link to="/settings" prefetch="render" preload={false}>
37
43
  Settings
38
44
  </Link>,
39
45
  );
40
46
 
41
- expect(capturedPreloads).toEqual(['intent']);
47
+ expect(capturedPreloads).toEqual([false]);
48
+ });
49
+
50
+ it('maps none prefetch to disabled TanStack preload', () => {
51
+ render(
52
+ <Link to="/settings" prefetch="none">
53
+ Settings
54
+ </Link>,
55
+ );
56
+
57
+ expect(capturedPreloads).toEqual([false]);
58
+ });
59
+
60
+ it.each([
61
+ 'intent',
62
+ 'render',
63
+ 'viewport',
64
+ ] as const)('maps %s prefetch to TanStack preload', prefetch => {
65
+ render(
66
+ <Link to="/settings" prefetch={prefetch}>
67
+ Settings
68
+ </Link>,
69
+ );
70
+
71
+ expect(capturedPreloads).toEqual([prefetch]);
72
+ });
73
+
74
+ it('defaults NavLink preload to viewport', () => {
75
+ render(<NavLink to="/settings">Settings</NavLink>);
76
+
77
+ expect(capturedPreloads).toEqual(['viewport']);
42
78
  });
43
79
  });
@@ -0,0 +1,46 @@
1
+ import { resolveRouterProvider } from '@modern-js/runtime/context';
2
+ import {
3
+ Form,
4
+ Link,
5
+ NavLink,
6
+ Outlet,
7
+ RouteActionResponseError,
8
+ useFetcher,
9
+ } from '../../src/runtime';
10
+ import { tanstackRouterCompatBindings } from '../../src/runtime/register';
11
+
12
+ const COMPAT_BINDINGS_SLOT = Symbol.for(
13
+ '@modern-js/plugin-tanstack:runtime-compat-bindings',
14
+ );
15
+
16
+ describe("'@modern-js/plugin-tanstack/runtime' import side effects", () => {
17
+ it('registers the tanstack router provider', () => {
18
+ expect(typeof resolveRouterProvider('tanstack')).toBe('function');
19
+ });
20
+
21
+ it("publishes the compat bindings consumed by '@modern-js/runtime/tanstack-router'", () => {
22
+ const bindings = (globalThis as Record<symbol, unknown>)[
23
+ COMPAT_BINDINGS_SLOT
24
+ ] as typeof tanstackRouterCompatBindings;
25
+
26
+ expect(bindings).toBeDefined();
27
+ expect(bindings).toBe(tanstackRouterCompatBindings);
28
+ expect(bindings.Form).toBe(Form);
29
+ expect(bindings.Link).toBe(Link);
30
+ expect(bindings.NavLink).toBe(NavLink);
31
+ expect(bindings.Outlet).toBe(Outlet);
32
+ expect(bindings.RouteActionResponseError).toBe(RouteActionResponseError);
33
+ expect(bindings.useFetcher).toBe(useFetcher);
34
+ });
35
+
36
+ it('keeps the first published bindings when a duplicate module copy evaluates', () => {
37
+ // Simulates a Module Federation remote evaluating its own copy of
38
+ // register.ts: `??=` must keep the established bindings.
39
+ const host = globalThis as Record<symbol, unknown>;
40
+ const established = host[COMPAT_BINDINGS_SLOT];
41
+ expect(established).toBeDefined();
42
+
43
+ host[COMPAT_BINDINGS_SLOT] ??= { duplicate: true };
44
+ expect(host[COMPAT_BINDINGS_SLOT]).toBe(established);
45
+ });
46
+ });