@bleedingdev/modern-js-plugin-tanstack 3.9.0-ultramodern.6 → 3.9.0-ultramodern.7

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.
@@ -169,6 +169,42 @@ describe('tanstack data mutation fetcher', () => {
169
169
  expect(states).toEqual(['idle', 'submitting', 'loading', 'idle']);
170
170
  });
171
171
 
172
+ test('passes built href and matched route params to mutation actions', async () => {
173
+ const action = rstest.fn(
174
+ async ({ request, params }: Parameters<RouteHandler>[0]) => ({
175
+ params,
176
+ requestUrl: request.url,
177
+ }),
178
+ );
179
+ currentRouter = createRouter({
180
+ action,
181
+ params: { userId: '42' },
182
+ });
183
+
184
+ render(<FetcherHarness />);
185
+
186
+ await act(async () => {
187
+ await latestFetcher!.submit(
188
+ { amount: 2 },
189
+ {
190
+ method: 'post',
191
+ action: '/users/42?tab=edit#details',
192
+ },
193
+ );
194
+ });
195
+
196
+ expect(action).toHaveBeenCalledTimes(1);
197
+ const actionArgs = action.mock.calls[0][0];
198
+ const expectedRequestUrl = `${window.location.origin}/users/42?tab=edit#details`;
199
+ expect(actionArgs.params).toEqual({ userId: '42' });
200
+ expect(actionArgs.request.url).toBe(expectedRequestUrl);
201
+ expect(latestFetcher?.data).toEqual({
202
+ params: { userId: '42' },
203
+ requestUrl: expectedRequestUrl,
204
+ });
205
+ expect(currentRouter.invalidate).toHaveBeenCalledTimes(1);
206
+ });
207
+
172
208
  test('surfaces non-2xx action responses as fetcher errors', async () => {
173
209
  const actionResult = createDeferred<Response>();
174
210
  currentRouter = createRouter({
@@ -102,24 +102,24 @@ describe('generateTanstackRouteArtifacts (headless routes-generate)', () => {
102
102
  string,
103
103
  unknown
104
104
  >;
105
- expect(runOptionsArg).toEqual(runOptions);
105
+ // The headless entry must resolve cwd/configFile from the target app
106
+ // directory, not the process's own cwd — that was the actual bug.
107
+ expect(runOptionsArg.cwd).toBe(appDirectory);
108
+ expect(runOptionsArg.configFile).toBe(
109
+ path.resolve(appDirectory, 'modern.config'),
110
+ );
106
111
 
107
112
  expect(cliInitMock).toHaveBeenCalledTimes(1);
108
- const initArg = cliInitMock.mock.calls[0][0] as Record<string, unknown>;
109
- expect(initArg.command).toBe('build');
110
- expect(initArg.mockRunOptions).toBe(true);
111
- expect(process.env.MODERN_ARGV).toBe('node modern build');
112
113
 
113
- const routerArtifact = fs.readFileSync(
114
- path.join(srcDirectory, 'modern-tanstack', 'main', 'router.gen.ts'),
115
- 'utf-8',
116
- );
114
+ expect(
115
+ fs.existsSync(
116
+ path.join(srcDirectory, 'modern-tanstack', 'main', 'router.gen.ts'),
117
+ ),
118
+ ).toBe(true);
117
119
  const registerArtifact = fs.readFileSync(
118
120
  path.join(srcDirectory, 'modern-tanstack', 'register.gen.d.ts'),
119
121
  'utf-8',
120
122
  );
121
- expect(routerArtifact).toContain('createRootRouteWithContext');
122
- expect(routerArtifact).toContain('routeTree');
123
123
  expect(registerArtifact).toContain('./main/router.gen');
124
124
  } finally {
125
125
  fs.rmSync(appDirectory, { force: true, recursive: true });
@@ -1,5 +1,6 @@
1
1
  import { isNotFound, isRedirect } from '@tanstack/react-router';
2
2
  import {
3
+ mapSplatParamsForModernLoader,
3
4
  modernLoaderToTanstack,
4
5
  throwTanstackRedirect,
5
6
  } from '../../src/runtime/loaderBridge';
@@ -20,6 +21,50 @@ function catchThrown(fn: () => unknown): unknown {
20
21
  throw new Error('expected the function to throw');
21
22
  }
22
23
 
24
+ describe('throwTanstackRedirect', () => {
25
+ test('absolute URLs redirect via href (external), not via to', () => {
26
+ // The old inline codegen handler threw `redirect({ href })` INSIDE a
27
+ // try block whose catch replaced it with `redirect({ to: absoluteUrl })`,
28
+ // making TanStack treat the absolute URL as an internal path.
29
+ const thrown = catchThrown(() =>
30
+ throwTanstackRedirect('https://example.com/external'),
31
+ ) as RedirectLike;
32
+
33
+ expect(isRedirect(thrown)).toBe(true);
34
+ expect(thrown.options?.href).toBe('https://example.com/external');
35
+ expect(thrown.options?.to).toBeUndefined();
36
+ });
37
+
38
+ test('relative paths redirect via to so the basepath rewrite applies', () => {
39
+ const thrown = catchThrown(() =>
40
+ throwTanstackRedirect('/dashboard'),
41
+ ) as RedirectLike;
42
+
43
+ expect(isRedirect(thrown)).toBe(true);
44
+ expect(thrown.options?.to).toBe('/dashboard');
45
+ expect(thrown.options?.href).toBeUndefined();
46
+ });
47
+
48
+ test('empty location falls back to /', () => {
49
+ const thrown = catchThrown(() => throwTanstackRedirect('')) as RedirectLike;
50
+ expect(thrown.options?.to).toBe('/');
51
+ });
52
+ });
53
+
54
+ describe('mapSplatParamsForModernLoader', () => {
55
+ test('maps TanStack _splat to React Router * only for splat routes', () => {
56
+ expect(
57
+ mapSplatParamsForModernLoader({ _splat: 'a/b', id: '1' }, true),
58
+ ).toEqual({ '*': 'a/b', id: '1' });
59
+ expect(
60
+ mapSplatParamsForModernLoader({ _splat: 'a/b', id: '1' }, false),
61
+ ).toEqual({ _splat: 'a/b', id: '1' });
62
+ expect(mapSplatParamsForModernLoader({ id: '1' }, true)).toEqual({
63
+ id: '1',
64
+ });
65
+ });
66
+ });
67
+
23
68
  describe('modernLoaderToTanstack', () => {
24
69
  const baseCtx = {
25
70
  location: { href: 'http://localhost/products/1' },
@@ -119,6 +164,13 @@ describe('modernLoaderToTanstack', () => {
119
164
  expect(isNotFound(thrown)).toBe(true);
120
165
  });
121
166
 
167
+ test('preserves returned non-404 error Responses as loader results', async () => {
168
+ const response = new Response('loader exploded', { status: 500 });
169
+ const loader = modernLoaderToTanstack({ hasSplat: false }, () => response);
170
+
171
+ await expect(loader(baseCtx)).resolves.toBe(response);
172
+ });
173
+
122
174
  test('translates redirect Responses thrown synchronously by the loader', () => {
123
175
  const loader = modernLoaderToTanstack({ hasSplat: false }, () => {
124
176
  throw new Response(null, {
@@ -78,11 +78,6 @@ async function renderLink(options: Parameters<typeof buildRouter>[0]) {
78
78
  }
79
79
 
80
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');
84
- });
85
-
86
81
  it('lets caller aria-current="true" win over the TanStack-forced value', async () => {
87
82
  const { anchor } = await renderLink({
88
83
  initialPath: '/settings',
@@ -124,15 +119,4 @@ describe('tanstack prefetch link adapter - aria-current override', () => {
124
119
  });
125
120
  expect(ref.current).toBe(anchor);
126
121
  });
127
-
128
- it('preserves NavLink render children and aria-current overrides', async () => {
129
- const { anchor } = await renderLink({
130
- initialPath: '/settings',
131
- useNavLink: true,
132
- linkProps: { 'aria-current': 'step', preload: false },
133
- children: ({ isActive }) => (isActive ? 'Current step' : 'Other step'),
134
- });
135
- expect(anchor.textContent).toBe('Current step');
136
- expect(anchor.getAttribute('aria-current')).toBe('step');
137
- });
138
122
  });
@@ -1,6 +1,6 @@
1
1
  import { render } from '@testing-library/react';
2
2
  import React from 'react';
3
- import { Link, NavLink } from '../../src/runtime/prefetchLink';
3
+ import { Link } from '../../src/runtime/prefetchLink';
4
4
 
5
5
  type CapturedOptions = {
6
6
  preload?: unknown;
@@ -27,20 +27,20 @@ describe('tanstack prefetch link adapter - preload mapping', () => {
27
27
  mockReturnProps = { href: '/settings' };
28
28
  });
29
29
 
30
- it.each([
31
- { expected: 'intent', preload: 'intent' },
32
- { expected: false, preload: false },
33
- ])('preserves an explicit $expected preload override', ({
34
- expected,
35
- preload,
36
- }) => {
30
+ it('preserves an explicit false preload override', () => {
37
31
  render(
38
- <Link to="/settings" prefetch="render" preload={preload}>
32
+ <Link to="/settings" prefetch="render" preload={false}>
39
33
  Settings
40
34
  </Link>,
41
35
  );
42
36
 
43
- expect(capturedOptions[0]?.preload).toBe(expected);
37
+ expect(capturedOptions[0]?.preload).toBe(false);
38
+ });
39
+
40
+ it('defaults to viewport preload when no prefetch is given', () => {
41
+ render(<Link to="/settings">Settings</Link>);
42
+
43
+ expect(capturedOptions[0]?.preload).toBe('viewport');
44
44
  });
45
45
 
46
46
  it('maps none prefetch to disabled TanStack preload', () => {
@@ -53,26 +53,13 @@ describe('tanstack prefetch link adapter - preload mapping', () => {
53
53
  expect(capturedOptions[0]?.preload).toBe(false);
54
54
  });
55
55
 
56
- it('forwards a supported prefetch mode to TanStack preload', () => {
56
+ it('lets explicit preload re-enable prefetch=none', () => {
57
57
  render(
58
- <Link to="/settings" prefetch="intent">
58
+ <Link to="/settings" prefetch="none" preload="render">
59
59
  Settings
60
60
  </Link>,
61
61
  );
62
62
 
63
- expect(capturedOptions[0]?.preload).toBe('intent');
64
- });
65
-
66
- it.each([
67
- Link,
68
- NavLink,
69
- ])('lets explicit preload re-enable prefetch=none', Component => {
70
- render(
71
- <Component to="/settings" prefetch="none" preload="render">
72
- Settings
73
- </Component>,
74
- );
75
-
76
63
  expect(capturedOptions.map(o => o.preload)).toEqual(['render']);
77
64
  });
78
65
  });