@@ -123,3 +123,113 @@ it('run with lazy + fallback language', async () => {
123123
124124 await expect ( file . toString ( ) ) . toMatchFileSnapshot ( './fixtures/d.out.html' )
125125} )
126+
127+ it ( 'lazy loading error handling with fallbackLanguage' , async ( ) => {
128+ using highlighter = await createHighlighter ( {
129+ themes : [ 'vitesse-light' ] ,
130+ langs : [ 'text' ] ,
131+ } )
132+
133+ // Create a mock highlighter that fails to load a specific language
134+ const mockHighlighter = {
135+ ...highlighter ,
136+ loadLanguage : async ( ...langs : Parameters < typeof highlighter . loadLanguage > ) => {
137+ const lang = langs [ 0 ] as string
138+ if ( lang === 'nonexistent-lang' ) {
139+ throw new Error ( `Language 'nonexistent-lang' not found` )
140+ }
141+ return highlighter . loadLanguage ( ...langs )
142+ } ,
143+ }
144+
145+ const markdown = '```nonexistent-lang\nconst x = 1\n```'
146+
147+ const file = await unified ( )
148+ . use ( remarkParse )
149+ . use ( remarkRehype )
150+ . use ( rehypeShikiFromHighlighter , mockHighlighter , {
151+ lazy : true ,
152+ theme : 'vitesse-light' ,
153+ fallbackLanguage : 'text' ,
154+ } )
155+ . use ( rehypeStringify )
156+ . process ( markdown )
157+
158+ // Should use fallback language (text) instead of throwing
159+ expect ( file . toString ( ) ) . toContain ( '<pre' )
160+ expect ( file . toString ( ) ) . toContain ( '<code' )
161+ } )
162+
163+ it ( 'lazy loading error handling with onError callback' , async ( ) => {
164+ using highlighter = await createHighlighter ( {
165+ themes : [ 'vitesse-light' ] ,
166+ langs : [ 'text' ] ,
167+ } )
168+
169+ const errors : unknown [ ] = [ ]
170+ const onError = ( error : unknown ) => {
171+ errors . push ( error )
172+ }
173+
174+ // Create a mock highlighter that fails to load a specific language
175+ const mockHighlighter = {
176+ ...highlighter ,
177+ loadLanguage : async ( ...langs : Parameters < typeof highlighter . loadLanguage > ) => {
178+ const lang = langs [ 0 ] as string
179+ if ( lang === 'failing-lang' ) {
180+ throw new Error ( `Language 'failing-lang' not found` )
181+ }
182+ return highlighter . loadLanguage ( ...langs )
183+ } ,
184+ }
185+
186+ const markdown = '```failing-lang\nconst x = 1\n```'
187+
188+ await unified ( )
189+ . use ( remarkParse )
190+ . use ( remarkRehype )
191+ . use ( rehypeShikiFromHighlighter , mockHighlighter , {
192+ lazy : true ,
193+ theme : 'vitesse-light' ,
194+ onError,
195+ } )
196+ . use ( rehypeStringify )
197+ . process ( markdown )
198+
199+ // onError should be called
200+ expect ( errors . length ) . toBeGreaterThan ( 0 )
201+ expect ( errors [ 0 ] ) . toBeInstanceOf ( Error )
202+ } )
203+
204+ it ( 'lazy loading error handling throws when no fallback or onError' , async ( ) => {
205+ using highlighter = await createHighlighter ( {
206+ themes : [ 'vitesse-light' ] ,
207+ langs : [ 'text' ] ,
208+ } )
209+
210+ // Create a mock highlighter that fails to load a specific language
211+ const mockHighlighter = {
212+ ...highlighter ,
213+ loadLanguage : async ( ...langs : Parameters < typeof highlighter . loadLanguage > ) => {
214+ const lang = langs [ 0 ] as string
215+ if ( lang === 'error-lang' ) {
216+ throw new Error ( `Language 'error-lang' not found` )
217+ }
218+ return highlighter . loadLanguage ( ...langs )
219+ } ,
220+ }
221+
222+ const markdown = '```error-lang\nconst x = 1\n```'
223+
224+ await expect (
225+ unified ( )
226+ . use ( remarkParse )
227+ . use ( remarkRehype )
228+ . use ( rehypeShikiFromHighlighter , mockHighlighter , {
229+ lazy : true ,
230+ theme : 'vitesse-light' ,
231+ } )
232+ . use ( rehypeStringify )
233+ . process ( markdown ) ,
234+ ) . rejects . toThrow ( )
235+ } )
0 commit comments