@@ -2,6 +2,7 @@ const vm = require('vm');
22const fs = require ( 'fs' ) ;
33const path = require ( 'path' ) ;
44const babel = require ( '@babel/core' ) ;
5+ const Module = require ( 'module' ) ;
56const React = require ( 'react' ) ;
67const enhancedResolve = require ( 'enhanced-resolve' ) ;
78const loader = require ( '../src/loader' ) ;
@@ -10,6 +11,8 @@ const loader = require('../src/loader');
1011jest . mock ( '@markdoc/next.js/runtime' , ( ) => require ( '../src/runtime' ) , { virtual : true } ) ;
1112
1213const source = fs . readFileSync ( require . resolve ( './fixture.md' ) , 'utf-8' ) ;
14+ const consoleErrorMock = jest . spyOn ( console , 'error' ) . mockImplementation ( ( ) => { } ) ;
15+ const consoleDebugMock = jest . spyOn ( console , 'debug' ) . mockImplementation ( ( ) => { } ) ;
1316
1417// https://stackoverflow.com/questions/53799385/how-can-i-convert-a-windows-path-to-posix-path-using-node-path
1518function normalizeAbsolutePath ( s ) {
@@ -27,12 +30,8 @@ function normalizeOperatingSystemPaths(s) {
2730 . replace ( / \/ r \/ n / g, '\\n' ) ;
2831}
2932
30- function evaluate ( output ) {
31- const { code} = babel . transformSync ( output ) ;
32- const exports = { } ;
33-
34- // https://stackoverflow.com/questions/38332094/how-can-i-mock-webpacks-require-context-in-jest
35- require . context = require . context = ( base = '.' ) => {
33+ function createRequireContext ( requireFn ) {
34+ return ( base = '.' ) => {
3635 const files = [ ] ;
3736
3837 function readDirectory ( directory ) {
@@ -49,20 +48,56 @@ function evaluate(output) {
4948
5049 readDirectory ( path . resolve ( __dirname , base ) ) ;
5150
52- return Object . assign ( require , { keys : ( ) => files } ) ;
51+ return Object . assign ( requireFn , { keys : ( ) => files } ) ;
52+ } ;
53+ }
54+
55+ function evaluate ( output , filename = path . join ( __dirname , 'pages/test/index.md' ) ) {
56+ const { code} = babel . transformSync ( output , { filename} ) ;
57+
58+ const resourceRequire = Module . createRequire ( filename ) ;
59+ const baseRequire = require ;
60+
61+ const customRequire = ( specifier ) => {
62+ if ( specifier . startsWith ( '.' ) || specifier . startsWith ( '/' ) ) {
63+ return resourceRequire ( specifier ) ;
64+ }
65+
66+ return baseRequire ( specifier ) ;
5367 } ;
5468
69+ customRequire . resolve = ( specifier ) => {
70+ if ( specifier . startsWith ( '.' ) || specifier . startsWith ( '/' ) ) {
71+ return resourceRequire . resolve ( specifier ) ;
72+ }
73+
74+ return baseRequire . resolve ( specifier ) ;
75+ } ;
76+
77+ customRequire . cache = baseRequire . cache ;
78+ customRequire . main = baseRequire . main ;
79+ customRequire . extensions = baseRequire . extensions ;
80+ customRequire . paths = baseRequire . paths ;
81+
82+ const context = createRequireContext ( customRequire ) ;
83+ customRequire . context = context ;
84+ require . context = context ;
85+
86+ const exports = { } ;
87+ const module = { exports} ;
88+
5589 vm . runInNewContext ( code , {
5690 exports,
57- require,
91+ module,
92+ require : customRequire ,
5893 console,
5994 } ) ;
6095
61- return exports ;
96+ return module . exports ;
6297}
6398
6499function options ( config = { } ) {
65- const dir = ` ${ '/Users/someone/a-next-js-repo' } / ${ config . appDir ? 'app' : 'pages' } ` ;
100+ const dir = path . join ( __dirname , config . appDir ? 'app' : 'pages' ) ;
66101
67102 const webpackThis = {
68103 context : __dirname ,
@@ -87,7 +122,7 @@ function options(config = {}) {
87122 resolve ( context , file , ( err , result ) => ( err ? rej ( err ) : res ( result ) ) )
88123 ) . then ( normalizeAbsolutePath ) ;
89124 } ,
90- resourcePath : dir + '/ test/ index.md',
125+ resourcePath : path . join ( dir , ' test' , ' index.md') ,
91126 } ;
92127
93128 return webpackThis ;
@@ -117,13 +152,14 @@ test('should fail build if invalid `schemaPath` is used', async () => {
117152} ) ;
118153
119154test ( 'file output is correct' , async ( ) => {
120- const output = await callLoader ( options ( ) , source ) ;
155+ const webpackThis = options ( ) ;
156+ const output = await callLoader ( webpackThis , source ) ;
121157
122158 expect ( normalizeOperatingSystemPaths ( output ) ) . toMatchSnapshot ( ) ;
123159
124- const page = evaluate ( output ) ;
160+ const page = evaluate ( output , webpackThis . resourcePath ) ;
125161
126- expect ( evaluate ( output ) ) . toEqual ( {
162+ expect ( page ) . toEqual ( {
127163 default : expect . any ( Function ) ,
128164 getStaticProps : expect . any ( Function ) ,
129165 markdoc : {
@@ -162,13 +198,14 @@ test('file output is correct', async () => {
162198} ) ;
163199
164200test ( 'app router' , async ( ) => {
165- const output = await callLoader ( options ( { appDir : true } ) , source ) ;
201+ const webpackThis = options ( { appDir : true } ) ;
202+ const output = await callLoader ( webpackThis , source ) ;
166203
167204 expect ( normalizeOperatingSystemPaths ( output ) ) . toMatchSnapshot ( ) ;
168205
169- const page = evaluate ( output ) ;
206+ const page = evaluate ( output , webpackThis . resourcePath ) ;
170207
171- expect ( evaluate ( output ) ) . toEqual ( {
208+ expect ( page ) . toEqual ( {
172209 default : expect . any ( Function ) ,
173210 markdoc : {
174211 frontmatter : {
@@ -183,8 +220,9 @@ test('app router', async () => {
183220} ) ;
184221
185222test ( 'app router metadata' , async ( ) => {
223+ const webpackThis = options ( { appDir : true } ) ;
186224 const output = await callLoader (
187- options ( { appDir : true } ) ,
225+ webpackThis ,
188226 source . replace ( '---' , '---\nmetadata:\n title: Metadata title' )
189227 ) ;
190228
@@ -199,40 +237,78 @@ test.each([
199237 [ 'schemas/files' , 'markdoc2' ] ,
200238 [ 'schemas/typescript' , source ] ,
201239] ) ( 'Custom schema path ("%s")' , async ( schemaPath , expectedChild ) => {
202- const output = await callLoader ( options ( { schemaPath} ) , source ) ;
240+ const webpackThis = options ( { schemaPath} ) ;
241+ const output = await callLoader ( webpackThis , source ) ;
203242
204- const page = evaluate ( output ) ;
243+ const page = evaluate ( output , webpackThis . resourcePath ) ;
205244
206245 const data = await page . getStaticProps ( { } ) ;
207246 expect ( data . props . markdoc . content . children [ 0 ] . children [ 0 ] ) . toEqual ( 'Custom title' ) ;
208247 expect ( data . props . markdoc . content . children [ 1 ] ) . toEqual ( expectedChild ) ;
209248} ) ;
210249
211250test ( 'Partials' , async ( ) => {
251+ const webpackThis = options ( { schemaPath : './schemas/partials' } ) ;
212252 const output = await callLoader (
213- options ( { schemaPath : './schemas/partials' } ) ,
253+ webpackThis ,
214254 `${ source } \n{% partial file="footer.md" /%}`
215255 ) ;
216256
217- const page = evaluate ( output ) ;
257+ const page = evaluate ( output , webpackThis . resourcePath ) ;
218258
219259 const data = await page . getStaticProps ( { } ) ;
220260 expect ( data . props . markdoc . content . children [ 1 ] . children [ 0 ] ) . toEqual ( 'footer' ) ;
221261} ) ;
222262
223263test ( 'Ejected config' , async ( ) => {
264+ const webpackThis = options ( { schemaPath : './schemas/ejectedConfig' } ) ;
224265 const output = await callLoader (
225- options ( { schemaPath : './schemas/ejectedConfig' } ) ,
266+ webpackThis ,
226267 `${ source } \n{% $product %}`
227268 ) ;
228269
229- const page = evaluate ( output ) ;
270+ const page = evaluate ( output , webpackThis . resourcePath ) ;
230271
231272 const data = await page . getStaticProps ( { } ) ;
232273 expect ( data . props . markdoc . content . children [ 1 ] ) . toEqual ( 'Extra value' ) ;
233274 expect ( data . props . markdoc . content . children [ 2 ] . children [ 0 ] ) . toEqual ( 'meal' ) ;
234275} ) ;
235276
277+ test ( 'falls back to relative schema imports when bare specifiers fail' , async ( ) => {
278+ const schemaDir = path . resolve ( __dirname , 'schemas/files' ) ;
279+ const resolveRequests = [ ] ;
280+ const webpackThis = {
281+ ...options ( { schemaPath : './schemas/files' } ) ,
282+ } ;
283+
284+ webpackThis . getResolve = ( ) => async ( _context , request ) => {
285+ resolveRequests . push ( request ) ;
286+ const target = {
287+ './tags' : path . join ( schemaDir , 'tags.js' ) ,
288+ './nodes' : path . join ( schemaDir , 'nodes.js' ) ,
289+ config : path . join ( schemaDir , 'config.js' ) ,
290+ './config' : path . join ( schemaDir , 'config.js' ) ,
291+ functions : path . join ( schemaDir , 'functions.js' ) ,
292+ './functions' : path . join ( schemaDir , 'functions.js' ) ,
293+ } [ request ] ;
294+
295+ if ( target ) {
296+ return normalizeAbsolutePath ( target ) ;
297+ }
298+
299+ throw new Error ( `Unable to resolve "${ request } "` ) ;
300+ } ;
301+
302+ const output = await callLoader ( webpackThis , source ) ;
303+
304+ expect ( resolveRequests ) . toEqual (
305+ expect . arrayContaining ( [ 'tags' , './tags' , 'nodes' , './nodes' ] )
306+ ) ;
307+
308+ const importMatch = output . match ( / i m p o r t \* a s t a g s f r o m ' ( [ ^ ' ] + ) ' / ) ;
309+ expect ( importMatch ?. [ 1 ] . startsWith ( '.' ) ) . toBe ( true ) ;
310+ } ) ;
311+
236312test ( 'HMR' , async ( ) => {
237313 const output = await callLoader (
238314 {
@@ -246,9 +322,10 @@ test('HMR', async () => {
246322} ) ;
247323
248324test ( 'mode="server"' , async ( ) => {
249- const output = await callLoader ( options ( { mode : 'server' } ) , source ) ;
325+ const webpackThis = options ( { mode : 'server' } ) ;
326+ const output = await callLoader ( webpackThis , source ) ;
250327
251- expect ( evaluate ( output ) ) . toEqual ( {
328+ expect ( evaluate ( output , webpackThis . resourcePath ) ) . toEqual ( {
252329 default : expect . any ( Function ) ,
253330 getServerSideProps : expect . any ( Function ) ,
254331 markdoc : {
@@ -270,26 +347,26 @@ test('import as frontend component', async () => {
270347
271348test ( 'Turbopack configuration' , ( ) => {
272349 const withMarkdoc = require ( '../src/index.js' ) ;
273-
350+
274351 // Test basic Turbopack configuration
275352 const config = withMarkdoc ( ) ( {
276353 pageExtensions : [ 'js' , 'md' , 'mdoc' ] ,
277354 turbopack : {
278355 rules : { } ,
279356 } ,
280357 } ) ;
281-
358+
282359 expect ( config . turbopack ) . toBeDefined ( ) ;
283360 expect ( config . turbopack . rules ) . toBeDefined ( ) ;
284361 expect ( config . turbopack . rules [ '*.md' ] ) . toBeDefined ( ) ;
285362 expect ( config . turbopack . rules [ '*.mdoc' ] ) . toBeDefined ( ) ;
286-
363+
287364 // Verify rule structure
288365 const mdRule = config . turbopack . rules [ '*.md' ] ;
289366 expect ( mdRule . loaders ) . toHaveLength ( 1 ) ;
290367 expect ( mdRule . loaders [ 0 ] . loader ) . toContain ( 'loader' ) ;
291368 expect ( mdRule . as ) . toBe ( '*.js' ) ;
292-
369+
293370 // Test that existing turbopack config is preserved
294371 const configWithExisting = withMarkdoc ( ) ( {
295372 pageExtensions : [ 'js' , 'md' ] ,
@@ -302,10 +379,10 @@ test('Turbopack configuration', () => {
302379 } ,
303380 } ,
304381 } ) ;
305-
382+
306383 expect ( configWithExisting . turbopack . rules [ '*.svg' ] ) . toBeDefined ( ) ;
307384 expect ( configWithExisting . turbopack . rules [ '*.md' ] ) . toBeDefined ( ) ;
308-
385+
309386 // Test custom extension
310387 const configWithCustomExt = withMarkdoc ( {
311388 extension : / \. ( m a r k d o w n | m d x ) $ / ,
@@ -315,7 +392,12 @@ test('Turbopack configuration', () => {
315392 rules : { } ,
316393 } ,
317394 } ) ;
318-
395+
319396 expect ( configWithCustomExt . turbopack . rules [ '*.markdown' ] ) . toBeDefined ( ) ;
320397 expect ( configWithCustomExt . turbopack . rules [ '*.mdx' ] ) . toBeDefined ( ) ;
321398} ) ;
399+
400+ afterAll ( ( ) => {
401+ consoleErrorMock . mockRestore ( ) ;
402+ consoleDebugMock . mockRestore ( ) ;
403+ } ) ;
0 commit comments