Skip to content

Commit 276457f

Browse files
committed
Automatically add Tab imports when converting static to dynamic
1 parent d797bce commit 276457f

File tree

2 files changed

+348
-40
lines changed

2 files changed

+348
-40
lines changed

src/__tests__/rehype-static-to-dynamic.test.ts

Lines changed: 113 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { describe, test } from 'node:test';
44
import rehypeStaticToDynamic, {
55
type RehypeStaticToDynamicElement as Element,
66
type RehypeStaticToDynamicElementChild as ElementChild,
7+
type RehypeStaticToDynamicMdxEsm as MdxEsm,
78
type RehypeStaticToDynamicRoot as Root,
89
type RehypeStaticToDynamicText as Text,
910
type RehypeStaticToDynamicTreeChild as TreeChild,
@@ -12,10 +13,11 @@ import rehypeStaticToDynamic, {
1213
/**
1314
* Helper function to create a test tree structure
1415
*/
15-
function createTestTree(code: string): Root {
16+
function createTestTree(code: string, extraChildren: MdxEsm[] = []): Root {
1617
const tree: Root = {
1718
type: 'root',
1819
children: [
20+
...extraChildren,
1921
{
2022
type: 'element',
2123
tagName: 'pre',
@@ -35,7 +37,7 @@ function createTestTree(code: string): Root {
3537
},
3638
],
3739
},
38-
],
40+
] as Root['children'],
3941
};
4042

4143
return tree;
@@ -46,7 +48,9 @@ function createTestTree(code: string): Root {
4648
*/
4749
function extractTransformedCode(tree: Root): string {
4850
// After transformation, the tree should have TabItem elements
49-
const tabsElement = tree.children[0];
51+
const tabsElement = tree.children.find(
52+
(child): child is Element => isElement(child) && child.tagName === 'Tabs'
53+
);
5054

5155
if (!isElement(tabsElement) || tabsElement.tagName !== 'Tabs') {
5256
throw new Error('Expected Tabs element not found');
@@ -92,6 +96,10 @@ function isElement(
9296
return Boolean(node && node.type === 'element');
9397
}
9498

99+
function isMdxEsmNode(node: TreeChild | undefined): node is MdxEsm {
100+
return Boolean(node && node.type === 'mdxjsEsm');
101+
}
102+
95103
function isTextNode(node: ElementChild): node is Text {
96104
return node.type === 'text';
97105
}
@@ -149,6 +157,77 @@ describe('rehype-static-to-dynamic', () => {
149157
assert.strictEqual(output, expected);
150158
});
151159

160+
test('adds Tabs imports when transforming static2dynamic code', async () => {
161+
const input = dedent /* javascript */ `
162+
import { createNativeStackNavigator } from '@react-navigation/native-stack';
163+
import { createStaticNavigation } from '@react-navigation/native';
164+
165+
const RootStack = createNativeStackNavigator({
166+
screens: {
167+
Home: HomeScreen,
168+
},
169+
});
170+
171+
const Navigation = createStaticNavigation(RootStack);
172+
173+
export default function App() {
174+
return <Navigation />;
175+
}
176+
`;
177+
178+
const tree = createTestTree(input);
179+
const plugin = rehypeStaticToDynamic();
180+
await plugin(tree);
181+
182+
const importNode = tree.children.find(isMdxEsmNode);
183+
184+
if (!importNode || !('value' in importNode)) {
185+
throw new Error('Expected MDX import node not found');
186+
}
187+
188+
assert.strictEqual(
189+
importNode.value,
190+
"import Tabs from '@theme/Tabs'\nimport TabItem from '@theme/TabItem'"
191+
);
192+
});
193+
194+
test('does not duplicate existing Tabs imports', async () => {
195+
const input = dedent /* javascript */ `
196+
import { createNativeStackNavigator } from '@react-navigation/native-stack';
197+
import { createStaticNavigation } from '@react-navigation/native';
198+
199+
const RootStack = createNativeStackNavigator({
200+
screens: {
201+
Home: HomeScreen,
202+
},
203+
});
204+
205+
const Navigation = createStaticNavigation(RootStack);
206+
207+
export default function App() {
208+
return <Navigation />;
209+
}
210+
`;
211+
212+
const tree = createTestTree(input, [
213+
{
214+
type: 'mdxjsEsm',
215+
value:
216+
"import Tabs from '@theme/Tabs'\nimport TabItem from '@theme/TabItem'",
217+
},
218+
]);
219+
const plugin = rehypeStaticToDynamic();
220+
await plugin(tree);
221+
222+
const tabImportNodes = tree.children.filter(
223+
(child) =>
224+
isMdxEsmNode(child) &&
225+
child.value.includes("import Tabs from '@theme/Tabs'")
226+
);
227+
228+
assert.strictEqual(tabImportNodes.length, 1);
229+
});
230+
152231
test('screen with options', async () => {
153232
const input = dedent /* javascript */ `
154233
import { createNativeStackNavigator, createNativeStackScreen } from '@react-navigation/native-stack';
@@ -912,14 +991,14 @@ describe('rehype-static-to-dynamic', () => {
912991
import { createDrawerNavigator } from '@react-navigation/drawer';
913992
import { createStaticNavigation } from '@react-navigation/native';
914993
915-
const Drawer = createDrawerNavigator({
994+
const RootDrawer = createDrawerNavigator({
916995
screens: {
917996
Home: HomeScreen,
918997
Profile: ProfileScreen,
919998
},
920999
});
9211000
922-
const Navigation = createStaticNavigation(Drawer);
1001+
const Navigation = createStaticNavigation(RootDrawer);
9231002
9241003
export default function App() {
9251004
return <Navigation />;
@@ -938,7 +1017,7 @@ describe('rehype-static-to-dynamic', () => {
9381017
9391018
const Drawer = createDrawerNavigator();
9401019
941-
function Drawer() {
1020+
function RootDrawer() {
9421021
return (
9431022
<Drawer.Navigator>
9441023
<Drawer.Screen name="Home" component={HomeScreen} />
@@ -950,7 +1029,7 @@ describe('rehype-static-to-dynamic', () => {
9501029
export default function App() {
9511030
return (
9521031
<NavigationContainer>
953-
<Drawer />
1032+
<RootDrawer />
9541033
</NavigationContainer>
9551034
);
9561035
}
@@ -959,6 +1038,33 @@ describe('rehype-static-to-dynamic', () => {
9591038
assert.strictEqual(output, expected);
9601039
});
9611040

1041+
test('throws on colliding navigator component names', async () => {
1042+
const input = dedent /* javascript */ `
1043+
import { createNativeStackNavigator } from '@react-navigation/native-stack';
1044+
import { createStaticNavigation } from '@react-navigation/native';
1045+
1046+
const Stack = createNativeStackNavigator({
1047+
screens: {
1048+
Home: HomeScreen,
1049+
},
1050+
});
1051+
1052+
const Navigation = createStaticNavigation(Stack);
1053+
1054+
export default function App() {
1055+
return <Navigation />;
1056+
}
1057+
`;
1058+
1059+
const tree = createTestTree(input);
1060+
const plugin = rehypeStaticToDynamic();
1061+
1062+
await assert.rejects(
1063+
plugin(tree),
1064+
/Rename the static navigator to avoid colliding with the generated dynamic code\./
1065+
);
1066+
});
1067+
9621068
test('nested navigator with highlight on screen property', async () => {
9631069
const input = dedent /* javascript */ `
9641070
import { createNativeStackNavigator } from '@react-navigation/native-stack';

0 commit comments

Comments
 (0)