@bleedingdev/modern-js-plugin-tanstack 3.5.0-ultramodern.4 → 3.5.0-ultramodern.41

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 (260) hide show
  1. package/dist/cjs/cli/artifacts.js +157 -0
  2. package/dist/cjs/cli/index.js +8 -79
  3. package/dist/cjs/cli/tanstackTypes/canonicalRoutes.js +113 -0
  4. package/dist/cjs/cli/tanstackTypes/shared.js +123 -0
  5. package/dist/cjs/cli/tanstackTypes/source.js +256 -0
  6. package/dist/cjs/cli/tanstackTypes.js +4 -350
  7. package/dist/cjs/runtime/blockingSubscribe.js +57 -0
  8. package/dist/cjs/runtime/{pluginCore.js → clientBasepath.js} +12 -19
  9. package/dist/cjs/runtime/clientHydration.js +117 -0
  10. package/dist/cjs/runtime/dataMutation.js +11 -176
  11. package/dist/cjs/runtime/formData.js +116 -0
  12. package/dist/cjs/runtime/loaderBridge.js +7 -11
  13. package/dist/cjs/runtime/plugin.js +21 -125
  14. package/dist/cjs/runtime/plugin.node.js +45 -125
  15. package/dist/cjs/runtime/pluginShared.js +87 -0
  16. package/dist/cjs/runtime/prefetchLink.js +15 -2
  17. package/dist/cjs/runtime/register.js +1 -47
  18. package/dist/cjs/runtime/routeTree/components.js +102 -0
  19. package/dist/cjs/runtime/routeTree/construction.js +152 -0
  20. package/dist/cjs/runtime/routeTree/index.js +47 -0
  21. package/dist/cjs/runtime/routeTree/loaders.js +158 -0
  22. package/dist/cjs/runtime/routeTree/paths.js +57 -0
  23. package/dist/cjs/runtime/routeTree/staticData.js +63 -0
  24. package/dist/cjs/runtime/routeTree/tanstackRoutes.js +57 -0
  25. package/dist/cjs/runtime/routeTree/types.js +18 -0
  26. package/dist/cjs/runtime/routeTree.js +7 -320
  27. package/dist/cjs/runtime/rsc/CompositeComponent.js +18 -13
  28. package/dist/cjs/runtime/rsc/ReplayableStream.js +0 -4
  29. package/dist/cjs/runtime/rsc/RscNodeRenderer.js +5 -16
  30. package/dist/cjs/runtime/rsc/client.js +8 -25
  31. package/dist/cjs/runtime/rsc/flightSerialization.js +132 -0
  32. package/dist/cjs/runtime/rsc/payloadErrors.js +99 -0
  33. package/dist/cjs/runtime/rsc/payloadFetch.js +105 -0
  34. package/dist/cjs/runtime/rsc/payloadRouter.js +21 -142
  35. package/dist/cjs/runtime/rsc/payloadRoutes.js +94 -0
  36. package/dist/cjs/runtime/rsc/payloadStorage.js +58 -0
  37. package/dist/cjs/runtime/rsc/server.js +14 -18
  38. package/dist/cjs/runtime/rsc/shared.js +68 -0
  39. package/dist/cjs/runtime/rsc/slotUsageSanitizer.js +1 -0
  40. package/dist/cjs/runtime/rsc/treeGetter.js +72 -0
  41. package/dist/cjs/runtime/shared/isAbsoluteUrl.js +47 -0
  42. package/dist/cjs/runtime/ssrManagedTags.js +103 -0
  43. package/dist/cjs/runtime/ssrPreload.js +89 -0
  44. package/dist/cjs/runtime/ssrTypes.js +18 -0
  45. package/dist/cjs/runtime/state.js +1 -1
  46. package/dist/cjs/runtime/submitAction.js +161 -0
  47. package/dist/cjs/runtime/utils.js +49 -86
  48. package/dist/esm/cli/artifacts.mjs +103 -0
  49. package/dist/esm/cli/index.mjs +8 -82
  50. package/dist/esm/cli/tanstackTypes/canonicalRoutes.mjs +75 -0
  51. package/dist/esm/cli/tanstackTypes/shared.mjs +61 -0
  52. package/dist/esm/cli/tanstackTypes/source.mjs +208 -0
  53. package/dist/esm/cli/tanstackTypes.mjs +2 -339
  54. package/dist/esm/runtime/blockingSubscribe.mjs +19 -0
  55. package/dist/esm/runtime/clientBasepath.mjs +10 -0
  56. package/dist/esm/runtime/clientHydration.mjs +70 -0
  57. package/dist/esm/runtime/dataMutation.mjs +2 -167
  58. package/dist/esm/runtime/formData.mjs +63 -0
  59. package/dist/esm/runtime/loaderBridge.mjs +5 -9
  60. package/dist/esm/runtime/plugin.mjs +14 -118
  61. package/dist/esm/runtime/plugin.node.mjs +41 -121
  62. package/dist/esm/runtime/pluginShared.mjs +40 -0
  63. package/dist/esm/runtime/prefetchLink.mjs +18 -5
  64. package/dist/esm/runtime/register.mjs +0 -14
  65. package/dist/esm/runtime/routeTree/components.mjs +55 -0
  66. package/dist/esm/runtime/routeTree/construction.mjs +111 -0
  67. package/dist/esm/runtime/routeTree/index.mjs +2 -0
  68. package/dist/esm/runtime/routeTree/loaders.mjs +117 -0
  69. package/dist/esm/runtime/routeTree/paths.mjs +16 -0
  70. package/dist/esm/runtime/routeTree/staticData.mjs +22 -0
  71. package/dist/esm/runtime/routeTree/tanstackRoutes.mjs +13 -0
  72. package/dist/esm/runtime/routeTree/types.mjs +0 -0
  73. package/dist/esm/runtime/routeTree.mjs +1 -318
  74. package/dist/esm/runtime/rsc/CompositeComponent.mjs +18 -13
  75. package/dist/esm/runtime/rsc/ReplayableStream.mjs +1 -1
  76. package/dist/esm/runtime/rsc/RscNodeRenderer.mjs +4 -15
  77. package/dist/esm/runtime/rsc/client.mjs +6 -26
  78. package/dist/esm/runtime/rsc/flightSerialization.mjs +88 -0
  79. package/dist/esm/runtime/rsc/payloadErrors.mjs +49 -0
  80. package/dist/esm/runtime/rsc/payloadFetch.mjs +55 -0
  81. package/dist/esm/runtime/rsc/payloadRouter.mjs +12 -133
  82. package/dist/esm/runtime/rsc/payloadRoutes.mjs +50 -0
  83. package/dist/esm/runtime/rsc/payloadStorage.mjs +14 -0
  84. package/dist/esm/runtime/rsc/server.mjs +17 -21
  85. package/dist/esm/runtime/rsc/shared.mjs +21 -0
  86. package/dist/esm/runtime/rsc/slotUsageSanitizer.mjs +1 -0
  87. package/dist/esm/runtime/rsc/treeGetter.mjs +34 -0
  88. package/dist/esm/runtime/shared/isAbsoluteUrl.mjs +9 -0
  89. package/dist/esm/runtime/ssrManagedTags.mjs +53 -0
  90. package/dist/esm/runtime/ssrPreload.mjs +51 -0
  91. package/dist/esm/runtime/ssrTypes.mjs +0 -0
  92. package/dist/esm/runtime/state.mjs +1 -1
  93. package/dist/esm/runtime/submitAction.mjs +120 -0
  94. package/dist/esm/runtime/utils.mjs +47 -75
  95. package/dist/esm-node/cli/artifacts.mjs +104 -0
  96. package/dist/esm-node/cli/index.mjs +8 -82
  97. package/dist/esm-node/cli/tanstackTypes/canonicalRoutes.mjs +76 -0
  98. package/dist/esm-node/cli/tanstackTypes/shared.mjs +62 -0
  99. package/dist/esm-node/cli/tanstackTypes/source.mjs +209 -0
  100. package/dist/esm-node/cli/tanstackTypes.mjs +2 -339
  101. package/dist/esm-node/runtime/blockingSubscribe.mjs +20 -0
  102. package/dist/esm-node/runtime/clientBasepath.mjs +11 -0
  103. package/dist/esm-node/runtime/clientHydration.mjs +71 -0
  104. package/dist/esm-node/runtime/dataMutation.mjs +2 -167
  105. package/dist/esm-node/runtime/formData.mjs +64 -0
  106. package/dist/esm-node/runtime/loaderBridge.mjs +5 -9
  107. package/dist/esm-node/runtime/plugin.mjs +14 -118
  108. package/dist/esm-node/runtime/plugin.node.mjs +41 -121
  109. package/dist/esm-node/runtime/pluginShared.mjs +41 -0
  110. package/dist/esm-node/runtime/prefetchLink.mjs +18 -5
  111. package/dist/esm-node/runtime/register.mjs +0 -14
  112. package/dist/esm-node/runtime/routeTree/components.mjs +56 -0
  113. package/dist/esm-node/runtime/routeTree/construction.mjs +112 -0
  114. package/dist/esm-node/runtime/routeTree/index.mjs +3 -0
  115. package/dist/esm-node/runtime/routeTree/loaders.mjs +118 -0
  116. package/dist/esm-node/runtime/routeTree/paths.mjs +17 -0
  117. package/dist/esm-node/runtime/routeTree/staticData.mjs +23 -0
  118. package/dist/esm-node/runtime/routeTree/tanstackRoutes.mjs +14 -0
  119. package/dist/esm-node/runtime/routeTree/types.mjs +1 -0
  120. package/dist/esm-node/runtime/routeTree.mjs +1 -318
  121. package/dist/esm-node/runtime/rsc/CompositeComponent.mjs +18 -13
  122. package/dist/esm-node/runtime/rsc/ReplayableStream.mjs +1 -1
  123. package/dist/esm-node/runtime/rsc/RscNodeRenderer.mjs +4 -15
  124. package/dist/esm-node/runtime/rsc/client.mjs +6 -26
  125. package/dist/esm-node/runtime/rsc/flightSerialization.mjs +89 -0
  126. package/dist/esm-node/runtime/rsc/payloadErrors.mjs +50 -0
  127. package/dist/esm-node/runtime/rsc/payloadFetch.mjs +56 -0
  128. package/dist/esm-node/runtime/rsc/payloadRouter.mjs +12 -133
  129. package/dist/esm-node/runtime/rsc/payloadRoutes.mjs +51 -0
  130. package/dist/esm-node/runtime/rsc/payloadStorage.mjs +15 -0
  131. package/dist/esm-node/runtime/rsc/server.mjs +17 -21
  132. package/dist/esm-node/runtime/rsc/shared.mjs +22 -0
  133. package/dist/esm-node/runtime/rsc/slotUsageSanitizer.mjs +1 -0
  134. package/dist/esm-node/runtime/rsc/treeGetter.mjs +35 -0
  135. package/dist/esm-node/runtime/shared/isAbsoluteUrl.mjs +10 -0
  136. package/dist/esm-node/runtime/ssrManagedTags.mjs +54 -0
  137. package/dist/esm-node/runtime/ssrPreload.mjs +52 -0
  138. package/dist/esm-node/runtime/ssrTypes.mjs +1 -0
  139. package/dist/esm-node/runtime/state.mjs +1 -1
  140. package/dist/esm-node/runtime/submitAction.mjs +121 -0
  141. package/dist/esm-node/runtime/utils.mjs +47 -75
  142. package/dist/types/cli/artifacts.d.ts +33 -0
  143. package/dist/types/cli/index.d.ts +2 -22
  144. package/dist/types/cli/tanstackTypes/canonicalRoutes.d.ts +22 -0
  145. package/dist/types/cli/tanstackTypes/shared.d.ts +20 -0
  146. package/dist/types/cli/tanstackTypes/source.d.ts +12 -0
  147. package/dist/types/cli/tanstackTypes.d.ts +3 -31
  148. package/dist/types/runtime/blockingSubscribe.d.ts +1 -0
  149. package/dist/types/runtime/clientBasepath.d.ts +1 -0
  150. package/dist/types/runtime/clientHydration.d.ts +10 -0
  151. package/dist/types/runtime/dataMutation.d.ts +3 -12
  152. package/dist/types/runtime/formData.d.ts +24 -0
  153. package/dist/types/runtime/loaderBridge.d.ts +2 -1
  154. package/dist/types/runtime/plugin.d.ts +1 -1
  155. package/dist/types/runtime/plugin.node.d.ts +1 -1
  156. package/dist/types/runtime/pluginShared.d.ts +38 -0
  157. package/dist/types/runtime/register.d.ts +1 -9
  158. package/dist/types/runtime/routeTree/components.d.ts +7 -0
  159. package/dist/types/runtime/routeTree/construction.d.ts +5 -0
  160. package/dist/types/runtime/routeTree/index.d.ts +2 -0
  161. package/dist/types/runtime/routeTree/loaders.d.ts +4 -0
  162. package/dist/types/runtime/routeTree/paths.d.ts +3 -0
  163. package/dist/types/runtime/routeTree/staticData.d.ts +19 -0
  164. package/dist/types/runtime/routeTree/tanstackRoutes.d.ts +4 -0
  165. package/dist/types/runtime/routeTree/types.d.ts +76 -0
  166. package/dist/types/runtime/routeTree.d.ts +1 -9
  167. package/dist/types/runtime/rsc/CompositeComponent.d.ts +1 -2
  168. package/dist/types/runtime/rsc/ReplayableStream.d.ts +3 -2
  169. package/dist/types/runtime/rsc/RscNodeRenderer.d.ts +2 -3
  170. package/dist/types/runtime/rsc/SlotContext.d.ts +2 -1
  171. package/dist/types/runtime/rsc/client.d.ts +2 -0
  172. package/dist/types/runtime/rsc/flightSerialization.d.ts +3 -0
  173. package/dist/types/runtime/rsc/payloadErrors.d.ts +6 -0
  174. package/dist/types/runtime/rsc/payloadFetch.d.ts +8 -0
  175. package/dist/types/runtime/rsc/payloadRouter.d.ts +2 -10
  176. package/dist/types/runtime/rsc/payloadRoutes.d.ts +40 -0
  177. package/dist/types/runtime/rsc/payloadStorage.d.ts +5 -0
  178. package/dist/types/runtime/rsc/shared.d.ts +18 -0
  179. package/dist/types/runtime/rsc/treeGetter.d.ts +1 -0
  180. package/dist/types/runtime/shared/isAbsoluteUrl.d.ts +1 -0
  181. package/dist/types/runtime/ssrManagedTags.d.ts +7 -0
  182. package/dist/types/runtime/ssrPreload.d.ts +2 -0
  183. package/dist/types/runtime/ssrTypes.d.ts +44 -0
  184. package/dist/types/runtime/state.d.ts +1 -1
  185. package/dist/types/runtime/submitAction.d.ts +14 -0
  186. package/dist/types/runtime/types.d.ts +4 -10
  187. package/dist/types/runtime/utils.d.ts +4 -24
  188. package/package.json +35 -14
  189. package/rstest.config.mts +6 -0
  190. package/src/cli/artifacts.ts +255 -0
  191. package/src/cli/index.ts +7 -185
  192. package/src/cli/tanstackTypes/canonicalRoutes.ts +157 -0
  193. package/src/cli/tanstackTypes/shared.ts +113 -0
  194. package/src/cli/tanstackTypes/source.ts +415 -0
  195. package/src/cli/tanstackTypes.ts +4 -647
  196. package/src/runtime/blockingSubscribe.ts +48 -0
  197. package/src/runtime/clientBasepath.ts +12 -0
  198. package/src/runtime/clientHydration.ts +146 -0
  199. package/src/runtime/dataMutation.tsx +9 -306
  200. package/src/runtime/formData.ts +153 -0
  201. package/src/runtime/loaderBridge.ts +11 -12
  202. package/src/runtime/plugin.node.tsx +76 -291
  203. package/src/runtime/plugin.tsx +21 -257
  204. package/src/runtime/pluginShared.ts +112 -0
  205. package/src/runtime/prefetchLink.tsx +78 -15
  206. package/src/runtime/register.ts +5 -37
  207. package/src/runtime/routeTree/components.ts +130 -0
  208. package/src/runtime/routeTree/construction.ts +238 -0
  209. package/src/runtime/routeTree/index.ts +7 -0
  210. package/src/runtime/routeTree/loaders.ts +232 -0
  211. package/src/runtime/routeTree/paths.ts +36 -0
  212. package/src/runtime/routeTree/staticData.ts +66 -0
  213. package/src/runtime/routeTree/tanstackRoutes.ts +36 -0
  214. package/src/runtime/routeTree/types.ts +87 -0
  215. package/src/runtime/routeTree.ts +5 -750
  216. package/src/runtime/rsc/CompositeComponent.tsx +29 -20
  217. package/src/runtime/rsc/ReplayableStream.ts +2 -2
  218. package/src/runtime/rsc/RscNodeRenderer.tsx +9 -27
  219. package/src/runtime/rsc/SlotContext.tsx +1 -1
  220. package/src/runtime/rsc/client.tsx +12 -40
  221. package/src/runtime/rsc/flightSerialization.ts +158 -0
  222. package/src/runtime/rsc/payloadErrors.ts +73 -0
  223. package/src/runtime/rsc/payloadFetch.ts +92 -0
  224. package/src/runtime/rsc/payloadRouter.ts +29 -234
  225. package/src/runtime/rsc/payloadRoutes.ts +122 -0
  226. package/src/runtime/rsc/payloadStorage.ts +35 -0
  227. package/src/runtime/rsc/server.tsx +20 -29
  228. package/src/runtime/rsc/shared.ts +70 -0
  229. package/src/runtime/rsc/slotUsageSanitizer.ts +4 -0
  230. package/src/runtime/rsc/treeGetter.tsx +52 -0
  231. package/src/runtime/shared/isAbsoluteUrl.ts +8 -0
  232. package/src/runtime/ssrManagedTags.ts +107 -0
  233. package/src/runtime/ssrPreload.ts +125 -0
  234. package/src/runtime/ssrTypes.ts +51 -0
  235. package/src/runtime/state.ts +9 -3
  236. package/src/runtime/submitAction.ts +201 -0
  237. package/src/runtime/types.ts +4 -18
  238. package/src/runtime/utils.tsx +76 -110
  239. package/src/types/react-server-dom-rspack.d.ts +56 -0
  240. package/tests/castRatchet.test.ts +85 -0
  241. package/tests/router/cli.test.ts +7 -0
  242. package/tests/router/dataMutation.test.tsx +59 -0
  243. package/tests/router/fixtures/router-gen.golden.txt +72 -0
  244. package/tests/router/flightSerialization.roundtrip.test.tsx +137 -0
  245. package/tests/router/generateRouteArtifacts.test.ts +106 -0
  246. package/tests/router/hooks.test.ts +14 -0
  247. package/tests/router/loaderBridge.test.ts +21 -0
  248. package/tests/router/prefetchLink.test.tsx +106 -59
  249. package/tests/router/prefetchLinkPreload.test.tsx +85 -0
  250. package/tests/router/register.test.ts +1 -39
  251. package/tests/router/routeTree.test.ts +51 -11
  252. package/tests/router/rsc.test.tsx +189 -0
  253. package/tests/router/rscPayloadRouterMatrix.test.ts +197 -0
  254. package/tests/router/slotUsageSanitizer.test.ts +23 -0
  255. package/tests/router/ssrPreload.test.ts +30 -0
  256. package/tests/router/tanstackTypes.test.ts +254 -1
  257. package/dist/esm/runtime/pluginCore.mjs +0 -14
  258. package/dist/esm-node/runtime/pluginCore.mjs +0 -15
  259. package/dist/types/runtime/pluginCore.d.ts +0 -21
  260. package/src/runtime/pluginCore.ts +0 -48
@@ -0,0 +1,106 @@
1
+ // Behavioral coverage for the headless routes-generate entry point: the
2
+ // export-shape test in cli.test.ts would pass even if the body were
3
+ // reverted, so this file pins the actual prepare-only drive contract —
4
+ // createRunOptions against the app dir, cli.init with build-command
5
+ // semantics, the MODERN_ARGV gate that makes analyze.onPrepare fire
6
+ // generateEntryCode, and the absolute config-file resolution that keeps
7
+ // headless runs (cwd != appDirectory) from feeding `false` into node's
8
+ // path APIs.
9
+ const createRunOptionsMock = rstest.fn(async (options: unknown) => ({
10
+ mockRunOptions: true,
11
+ received: options,
12
+ }));
13
+ const cliInitMock = rstest.fn(async (options: unknown) => ({
14
+ appContext: {},
15
+ received: options,
16
+ }));
17
+
18
+ rstest.mock('@modern-js/app-tools/cli/run', () => ({
19
+ createRunOptions: createRunOptionsMock,
20
+ }));
21
+ rstest.mock('@modern-js/plugin/cli', () => ({
22
+ cli: { init: cliInitMock },
23
+ }));
24
+
25
+ import fs from 'node:fs';
26
+ import os from 'node:os';
27
+ import path from 'node:path';
28
+ import { INTERNAL_RUNTIME_PLUGINS } from '@modern-js/utils';
29
+ import { generateTanstackRouteArtifacts } from '../../src/cli';
30
+
31
+ function makeAppDir(withConfig: boolean): string {
32
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'tanstack-routes-gen-'));
33
+ if (withConfig) {
34
+ fs.writeFileSync(
35
+ path.join(dir, 'modern.config.ts'),
36
+ 'export default {};\n',
37
+ 'utf-8',
38
+ );
39
+ }
40
+ return dir;
41
+ }
42
+
43
+ describe('generateTanstackRouteArtifacts (headless routes-generate)', () => {
44
+ beforeEach(() => {
45
+ createRunOptionsMock.mockClear();
46
+ cliInitMock.mockClear();
47
+ });
48
+
49
+ it('drives a prepare-only cli.init with build-command semantics', async () => {
50
+ const previousArgv = process.env.MODERN_ARGV;
51
+ const appDirectory = makeAppDir(true);
52
+
53
+ try {
54
+ await generateTanstackRouteArtifacts({
55
+ appDirectory,
56
+ version: '9.9.9-test',
57
+ });
58
+
59
+ expect(createRunOptionsMock).toHaveBeenCalledTimes(1);
60
+ const runOptionsArg = createRunOptionsMock.mock.calls[0][0] as Record<
61
+ string,
62
+ unknown
63
+ >;
64
+ expect(runOptionsArg.cwd).toBe(appDirectory);
65
+ expect(runOptionsArg.version).toBe('9.9.9-test');
66
+ expect(runOptionsArg.internalPlugins).toBe(INTERNAL_RUNTIME_PLUGINS);
67
+
68
+ // The config file is resolved to an absolute base path against the app
69
+ // directory (not process.cwd()). Without this, a headless run from a
70
+ // different cwd finds no config, `findExists` returns `false`, and that
71
+ // boolean reaches `path.isAbsolute` deep inside plugin/cli.
72
+ expect(runOptionsArg.configFile).toBe(
73
+ path.resolve(appDirectory, 'modern.config'),
74
+ );
75
+
76
+ expect(cliInitMock).toHaveBeenCalledTimes(1);
77
+ const initArg = cliInitMock.mock.calls[0][0] as Record<string, unknown>;
78
+ expect(initArg.command).toBe('build');
79
+ expect(initArg.mockRunOptions).toBe(true);
80
+
81
+ // checkIsBuildCommands() resolves the command from MODERN_ARGV — this
82
+ // is what lets analyze.onPrepare fire generateEntryCode headlessly.
83
+ expect(process.env.MODERN_ARGV).toBe('node modern build');
84
+ } finally {
85
+ fs.rmSync(appDirectory, { force: true, recursive: true });
86
+ if (previousArgv === undefined) {
87
+ delete process.env.MODERN_ARGV;
88
+ } else {
89
+ process.env.MODERN_ARGV = previousArgv;
90
+ }
91
+ }
92
+ });
93
+
94
+ it('throws a clear error (never a boolean path crash) when no config file exists', async () => {
95
+ const appDirectory = makeAppDir(false);
96
+
97
+ try {
98
+ await expect(
99
+ generateTanstackRouteArtifacts({ appDirectory }),
100
+ ).rejects.toThrow(/Unable to locate a Modern\.js config file/u);
101
+ expect(createRunOptionsMock).not.toHaveBeenCalled();
102
+ } finally {
103
+ fs.rmSync(appDirectory, { force: true, recursive: true });
104
+ }
105
+ });
106
+ });
@@ -3,8 +3,10 @@ import {
3
3
  routerProviderRegistryHooks,
4
4
  } from '@modern-js/runtime/context';
5
5
  import * as tanstackHooks from '../../src/runtime/hooks';
6
+ import { applyRouterRuntimeState } from '../../src/runtime/lifecycle';
6
7
  import { tanstackRouterPlugin as browserPlugin } from '../../src/runtime/plugin';
7
8
  import { tanstackRouterPlugin as nodePlugin } from '../../src/runtime/plugin.node';
9
+ import { getTanstackRouterState } from '../../src/runtime/state';
8
10
 
9
11
  describe('tanstack router hooks single declaration source', () => {
10
12
  test('re-exports the canonical hook instances owned by @modern-js/runtime', () => {
@@ -24,3 +26,15 @@ describe('tanstack router hooks single declaration source', () => {
24
26
  expect(nodePlugin().registryHooks).toBe(routerProviderRegistryHooks);
25
27
  });
26
28
  });
29
+
30
+ describe('tanstack router runtime state accessor', () => {
31
+ test('returns undefined before a tanstack router instance is created', () => {
32
+ const context = {};
33
+
34
+ applyRouterRuntimeState(context as never, {
35
+ framework: 'tanstack',
36
+ });
37
+
38
+ expect(getTanstackRouterState(context)).toBeUndefined();
39
+ });
40
+ });
@@ -117,6 +117,27 @@ describe('modernLoaderToTanstack', () => {
117
117
  expect(seen.context).toEqual({ user: 'u1' });
118
118
  });
119
119
 
120
+ test('uses current location when context carries a previous request', async () => {
121
+ const seen: { request?: Request } = {};
122
+ const loader = modernLoaderToTanstack({ hasSplat: false }, (args: any) => {
123
+ seen.request = args.request;
124
+ return { ok: true };
125
+ });
126
+
127
+ await expect(
128
+ loader({
129
+ ...baseCtx,
130
+ location: { href: 'http://localhost/products/2?sort=price' },
131
+ context: {
132
+ request: new Request('http://localhost/products/1?sort=name'),
133
+ },
134
+ }),
135
+ ).resolves.toEqual({ ok: true });
136
+
137
+ expect(seen.request).toBeInstanceOf(Request);
138
+ expect(seen.request?.url).toBe('http://localhost/products/2?sort=price');
139
+ });
140
+
120
141
  test('translates an absolute-URL redirect Response into redirect({ href })', async () => {
121
142
  const loader = modernLoaderToTanstack({ hasSplat: false }, () =>
122
143
  Response.redirect('https://example.com/away', 302),
@@ -1,79 +1,126 @@
1
- import { render } from '@testing-library/react';
1
+ import { createMemoryHistory } from '@tanstack/history';
2
+ import {
3
+ createRootRoute,
4
+ createRoute,
5
+ createRouter,
6
+ RouterProvider,
7
+ } from '@tanstack/react-router';
8
+ import { render, waitFor } from '@testing-library/react';
2
9
  import React from 'react';
3
10
  import { Link, NavLink } from '../../src/runtime/prefetchLink';
4
11
 
5
- type MockLinkProps = {
6
- children?: React.ReactNode;
7
- preload?: unknown;
8
- };
9
-
10
- let capturedPreloads: unknown[] = [];
11
-
12
- rstest.mock('@tanstack/react-router', () => ({
13
- Link: (props: MockLinkProps) => {
14
- capturedPreloads.push(props.preload);
15
- return <a href="/settings">{props.children}</a>;
16
- },
17
- }));
18
-
19
- describe('tanstack prefetch link adapter', () => {
20
- beforeEach(() => {
21
- capturedPreloads = [];
12
+ // Real-router harness (no module mocking): STATIC_ACTIVE_PROPS (the
13
+ // TanStack behavior this adapter works around) is injected by
14
+ // useLinkProps() based on live route match state, which a mocked
15
+ // useLinkProps() cannot reproduce faithfully. We tried spying on
16
+ // useLinkProps via rstest.mock()+importActual while re-exporting the rest
17
+ // of the module untouched, but rstest's module-mock hoisting resolves the
18
+ // "actual" import for a module through its own in-progress mock
19
+ // registration, so every named export (createRootRoute included) comes
20
+ // back undefined -- see the sibling prefetchLinkPreload.test.tsx file for
21
+ // the fully-mocked preload-mapping coverage instead. This file renders
22
+ // through an actual createRouter()/RouterProvider tree with in-memory
23
+ // history so the active/inactive aria-current behavior is exercised for
24
+ // real.
25
+ function buildRouter(options: {
26
+ initialPath: string;
27
+ linkProps?: Record<string, unknown>;
28
+ useNavLink?: boolean;
29
+ children?:
30
+ | React.ReactNode
31
+ | ((state: { isActive: boolean }) => React.ReactNode);
32
+ }) {
33
+ // The Link lives on the always-rendered root component (not a route
34
+ // that's only matched at "/settings") so the "inactive link" case -
35
+ // sitting at "/" while linking to "/settings" - actually mounts it.
36
+ const rootRoute = createRootRoute({
37
+ component: () => {
38
+ const Component = options.useNavLink ? NavLink : Link;
39
+ return (
40
+ <Component to="/settings" {...(options.linkProps ?? {})}>
41
+ {options.children ?? 'Settings'}
42
+ </Component>
43
+ );
44
+ },
22
45
  });
23
-
24
- it('defaults TanStack preload to viewport', () => {
25
- render(<Link to="/settings">Settings</Link>);
26
-
27
- expect(capturedPreloads).toEqual(['viewport']);
46
+ const homeRoute = createRoute({
47
+ getParentRoute: () => rootRoute,
48
+ path: '/',
49
+ component: () => null,
50
+ });
51
+ const settingsRoute = createRoute({
52
+ getParentRoute: () => rootRoute,
53
+ path: '/settings',
54
+ component: () => null,
28
55
  });
29
56
 
30
- it('preserves explicit preload', () => {
31
- render(
32
- <Link to="/settings" prefetch="render" preload="intent">
33
- Settings
34
- </Link>,
35
- );
57
+ const routeTree = rootRoute.addChildren([homeRoute, settingsRoute]);
36
58
 
37
- expect(capturedPreloads).toEqual(['intent']);
59
+ return createRouter({
60
+ routeTree,
61
+ history: createMemoryHistory({ initialEntries: [options.initialPath] }),
62
+ });
63
+ }
64
+
65
+ async function renderLink(options: Parameters<typeof buildRouter>[0]) {
66
+ const router = buildRouter(options);
67
+ const utils = render(<RouterProvider router={router} />);
68
+
69
+ const anchor = await waitFor(() => {
70
+ const found = utils.container.querySelector('a[href="/settings"]');
71
+ if (!found) {
72
+ throw new Error('anchor not rendered yet');
73
+ }
74
+ return found as HTMLAnchorElement;
38
75
  });
39
76
 
40
- it('preserves explicit disabled preload', () => {
41
- render(
42
- <Link to="/settings" prefetch="render" preload={false}>
43
- Settings
44
- </Link>,
45
- );
77
+ return { anchor, ...utils };
78
+ }
46
79
 
47
- expect(capturedPreloads).toEqual([false]);
80
+ describe('tanstack prefetch link adapter - aria-current override', () => {
81
+ it('defaults active link to aria-current="page" when caller passes nothing', async () => {
82
+ const { anchor } = await renderLink({ initialPath: '/settings' });
83
+ expect(anchor.getAttribute('aria-current')).toBe('page');
48
84
  });
49
85
 
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]);
86
+ it('lets caller aria-current="true" win over the TanStack-forced value', async () => {
87
+ const { anchor } = await renderLink({
88
+ initialPath: '/settings',
89
+ linkProps: { 'aria-current': 'true' },
90
+ });
91
+ expect(anchor.getAttribute('aria-current')).toBe('true');
92
+ expect(anchor.outerHTML.match(/aria-current=/g)).toHaveLength(1);
58
93
  });
59
94
 
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
- );
95
+ it('suppresses aria-current entirely when caller passes false', async () => {
96
+ const { anchor } = await renderLink({
97
+ initialPath: '/settings',
98
+ linkProps: { 'aria-current': false },
99
+ });
100
+ expect(anchor.hasAttribute('aria-current')).toBe(false);
101
+ });
70
102
 
71
- expect(capturedPreloads).toEqual([prefetch]);
103
+ it('leaves an inactive link untouched when caller passes nothing', async () => {
104
+ const { anchor } = await renderLink({ initialPath: '/' });
105
+ expect(anchor.hasAttribute('aria-current')).toBe(false);
106
+ expect(anchor.getAttribute('data-status')).not.toBe('active');
72
107
  });
73
108
 
74
- it('defaults NavLink preload to viewport', () => {
75
- render(<NavLink to="/settings">Settings</NavLink>);
109
+ it('resolves render-prop children with the active state like TanStack Link', async () => {
110
+ const { anchor } = await renderLink({
111
+ initialPath: '/settings',
112
+ children: ({ isActive }: { isActive: boolean }) =>
113
+ isActive ? 'Active settings' : 'Settings',
114
+ });
115
+ expect(anchor.textContent).toBe('Active settings');
116
+ });
76
117
 
77
- expect(capturedPreloads).toEqual(['viewport']);
118
+ it('resolves render-prop children as inactive on a non-matching route', async () => {
119
+ const { anchor } = await renderLink({
120
+ initialPath: '/',
121
+ children: ({ isActive }: { isActive: boolean }) =>
122
+ isActive ? 'Active settings' : 'Settings',
123
+ });
124
+ expect(anchor.textContent).toBe('Settings');
78
125
  });
79
126
  });
@@ -0,0 +1,85 @@
1
+ import { render } from '@testing-library/react';
2
+ import React from 'react';
3
+ import { Link, NavLink } from '../../src/runtime/prefetchLink';
4
+
5
+ type CapturedOptions = {
6
+ preload?: unknown;
7
+ };
8
+
9
+ type MockLinkAnchorProps = Record<string, unknown> & {
10
+ href?: string;
11
+ ref?: unknown;
12
+ };
13
+
14
+ let capturedOptions: CapturedOptions[] = [];
15
+ let mockReturnProps: MockLinkAnchorProps = { href: '/settings' };
16
+
17
+ rstest.mock('@tanstack/react-router', () => ({
18
+ useLinkProps: (options: CapturedOptions) => {
19
+ capturedOptions.push(options);
20
+ return mockReturnProps;
21
+ },
22
+ }));
23
+
24
+ describe('tanstack prefetch link adapter - preload mapping', () => {
25
+ beforeEach(() => {
26
+ capturedOptions = [];
27
+ mockReturnProps = { href: '/settings' };
28
+ });
29
+
30
+ it('defaults TanStack preload to viewport', () => {
31
+ render(<Link to="/settings">Settings</Link>);
32
+
33
+ expect(capturedOptions.map(o => o.preload)).toEqual(['viewport']);
34
+ });
35
+
36
+ it('preserves explicit preload', () => {
37
+ render(
38
+ <Link to="/settings" prefetch="render" preload="intent">
39
+ Settings
40
+ </Link>,
41
+ );
42
+
43
+ expect(capturedOptions.map(o => o.preload)).toEqual(['intent']);
44
+ });
45
+
46
+ it('preserves explicit disabled preload', () => {
47
+ render(
48
+ <Link to="/settings" prefetch="render" preload={false}>
49
+ Settings
50
+ </Link>,
51
+ );
52
+
53
+ expect(capturedOptions.map(o => o.preload)).toEqual([false]);
54
+ });
55
+
56
+ it('maps none prefetch to disabled TanStack preload', () => {
57
+ render(
58
+ <Link to="/settings" prefetch="none">
59
+ Settings
60
+ </Link>,
61
+ );
62
+
63
+ expect(capturedOptions.map(o => o.preload)).toEqual([false]);
64
+ });
65
+
66
+ it.each([
67
+ 'intent',
68
+ 'render',
69
+ 'viewport',
70
+ ] as const)('maps %s prefetch to TanStack preload', prefetch => {
71
+ render(
72
+ <Link to="/settings" prefetch={prefetch}>
73
+ Settings
74
+ </Link>,
75
+ );
76
+
77
+ expect(capturedOptions.map(o => o.preload)).toEqual([prefetch]);
78
+ });
79
+
80
+ it('defaults NavLink preload to viewport', () => {
81
+ render(<NavLink to="/settings">Settings</NavLink>);
82
+
83
+ expect(capturedOptions.map(o => o.preload)).toEqual(['viewport']);
84
+ });
85
+ });
@@ -1,46 +1,8 @@
1
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
- );
2
+ import '../../src/runtime/register';
15
3
 
16
4
  describe("'@modern-js/plugin-tanstack/runtime' import side effects", () => {
17
5
  it('registers the tanstack router provider', () => {
18
6
  expect(typeof resolveRouterProvider('tanstack')).toBe('function');
19
7
  });
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
8
  });
@@ -15,7 +15,7 @@ import {
15
15
  getModernRouteIdsFromMatches,
16
16
  } from '../../src/runtime/routeTree';
17
17
  import { __setTanstackRscPayloadDecoderForTests } from '../../src/runtime/rsc/payloadRouter';
18
- import { createRouteObjectsFromConfig } from '../../src/runtime/utils';
18
+ import { createTanstackRouteObjectsFromConfig } from '../../src/runtime/utils';
19
19
 
20
20
  type LoaderArgs = {
21
21
  params: Record<string, string>;
@@ -151,15 +151,15 @@ describe('tanstack runtime public exports', () => {
151
151
  };
152
152
 
153
153
  expect(packageJson.exports['./runtime/rsc']).toBeUndefined();
154
- expect(packageJson.exports['./runtime/rsc/client']).toBeUndefined();
155
- expect(packageJson.exports['./runtime/rsc/server']).toBeUndefined();
154
+ expect(packageJson.exports['./runtime/rsc/client']).toBeDefined();
155
+ expect(packageJson.exports['./runtime/rsc/server']).toBeDefined();
156
156
  expect(packageJson.typesVersions?.['*']?.['runtime/rsc']).toBeUndefined();
157
- expect(
158
- packageJson.typesVersions?.['*']?.['runtime/rsc/client'],
159
- ).toBeUndefined();
160
- expect(
161
- packageJson.typesVersions?.['*']?.['runtime/rsc/server'],
162
- ).toBeUndefined();
157
+ expect(packageJson.typesVersions?.['*']?.['runtime/rsc/client']).toEqual([
158
+ './dist/types/runtime/rsc/client.d.ts',
159
+ ]);
160
+ expect(packageJson.typesVersions?.['*']?.['runtime/rsc/server']).toEqual([
161
+ './dist/types/runtime/rsc/server.d.ts',
162
+ ]);
163
163
  });
164
164
  });
165
165
 
@@ -201,6 +201,41 @@ describe('tanstack route tree from RouteObject[]', () => {
201
201
  expect(userMatch?.loaderData).toEqual({ id: '123' });
202
202
  });
203
203
 
204
+ test('keeps unnamed sibling pathless layouts distinct', async () => {
205
+ const routes: RouteObject[] = [
206
+ {
207
+ Component: () => null,
208
+ children: [
209
+ {
210
+ path: 'alpha',
211
+ loader: () => ({ value: 'alpha' }),
212
+ Component: () => null,
213
+ },
214
+ ],
215
+ },
216
+ {
217
+ Component: () => null,
218
+ children: [
219
+ {
220
+ path: 'beta',
221
+ loader: () => ({ value: 'beta' }),
222
+ Component: () => null,
223
+ },
224
+ ],
225
+ },
226
+ ];
227
+
228
+ const routeTree = createRouteTreeFromRouteObjects(routes);
229
+ const router = await loadRouteTree(routeTree, '/beta');
230
+
231
+ const matchedLoaderData = router.state.matches
232
+ .map(match => match.loaderData)
233
+ .filter(Boolean);
234
+
235
+ expect(matchedLoaderData).toContainEqual({ value: 'beta' });
236
+ expect(matchedLoaderData).not.toContainEqual({ value: 'alpha' });
237
+ });
238
+
204
239
  test('reports native TanStack unknown routes as HTTP 404', async () => {
205
240
  const routes: RouteObject[] = [
206
241
  {
@@ -774,13 +809,16 @@ describe('tanstack route tree from RouteObject[]', () => {
774
809
  path: 'client',
775
810
  clientData: './client.data',
776
811
  data: './data',
812
+ hasAction: true,
813
+ hasClientLoader: true,
814
+ hasLoader: true,
777
815
  inValidSSRRoute: true,
778
816
  isClientComponent: true,
779
817
  },
780
818
  ],
781
819
  },
782
820
  ];
783
- const routeObjects = createRouteObjectsFromConfig({
821
+ const routeObjects = createTanstackRouteObjectsFromConfig({
784
822
  routesConfig: { routes: modernRoutes },
785
823
  });
786
824
  const routeTree = createRouteTreeFromRouteObjects(routeObjects || []);
@@ -795,7 +833,9 @@ describe('tanstack route tree from RouteObject[]', () => {
795
833
 
796
834
  expect(clientRoute.options.ssr).toBe(false);
797
835
  expect(clientRoute.options.staticData).toMatchObject({
836
+ modernRouteHasAction: true,
798
837
  modernRouteHasClientLoader: true,
838
+ modernRouteHasLoader: true,
799
839
  modernRouteIsClientComponent: true,
800
840
  });
801
841
  });
@@ -825,7 +865,7 @@ describe('tanstack route tree from RouteObject[]', () => {
825
865
  },
826
866
  ];
827
867
 
828
- const routeObjects = createRouteObjectsFromConfig({
868
+ const routeObjects = createTanstackRouteObjectsFromConfig({
829
869
  routesConfig: { routes: modernRoutes },
830
870
  });
831
871
  const rootRoute = routeObjects?.[0];