@@ -11,6 +11,7 @@ vi.mock('../src/env', async (importOriginal) => {
1111 } ;
1212} ) ;
1313
14+ import { docsBlockNoteSchema } from '@/blockSpecs' ;
1415import { initApp } from '@/servers' ;
1516
1617import {
@@ -300,6 +301,261 @@ describe('Conversion Testing', () => {
300301 expect ( response . body ) . toStrictEqual ( expectedBlocks ) ;
301302 } ) ;
302303
304+ test ( 'POST /api/convert Yjs to HTML with callout block' , async ( ) => {
305+ const app = initApp ( ) ;
306+ const editor = ServerBlockNoteEditor . create ( { schema : docsBlockNoteSchema } ) ;
307+ const blocks = [
308+ {
309+ type : 'callout' as const ,
310+ props : { emoji : '⚠️' , backgroundColor : 'yellow' } ,
311+ content : [ { type : 'text' as const , text : 'Be careful' , styles : { } } ] ,
312+ } ,
313+ ] ;
314+ const yDocument = editor . blocksToYDoc ( blocks , 'document-store' ) ;
315+ const yjsUpdate = Y . encodeStateAsUpdate ( yDocument ) ;
316+ const response = await request ( app )
317+ . post ( '/api/convert' )
318+ . set ( 'origin' , origin )
319+ . set ( 'authorization' , `Bearer ${ apiKey } ` )
320+ . set ( 'content-type' , 'application/vnd.yjs.doc' )
321+ . set ( 'accept' , 'text/html' )
322+ . send ( Buffer . from ( yjsUpdate ) ) ;
323+
324+ expect ( response . status ) . toBe ( 200 ) ;
325+ expect ( response . text ) . toContain ( '<aside' ) ;
326+ expect ( response . text ) . toContain ( 'role="note"' ) ;
327+ expect ( response . text ) . toContain ( 'data-emoji="⚠️"' ) ;
328+ expect ( response . text ) . toContain ( 'data-background-color="yellow"' ) ;
329+ expect ( response . text ) . toContain ( 'Be careful' ) ;
330+ // The inner emoji span is marked so downstream parsers can drop it
331+ // (the canonical emoji is on the <aside>).
332+ expect ( response . text ) . toContain (
333+ '<span aria-hidden="true" data-emoji="⚠️">' ,
334+ ) ;
335+ } ) ;
336+
337+ test ( 'POST /api/convert Yjs to Markdown preserves callout content' , async ( ) => {
338+ const app = initApp ( ) ;
339+ const editor = ServerBlockNoteEditor . create ( { schema : docsBlockNoteSchema } ) ;
340+ const blocks = [
341+ {
342+ type : 'callout' as const ,
343+ props : { emoji : '⚠️' , backgroundColor : 'yellow' } ,
344+ content : [ { type : 'text' as const , text : 'Be careful' , styles : { } } ] ,
345+ } ,
346+ ] ;
347+ const yDocument = editor . blocksToYDoc ( blocks , 'document-store' ) ;
348+ const yjsUpdate = Y . encodeStateAsUpdate ( yDocument ) ;
349+ const response = await request ( app )
350+ . post ( '/api/convert' )
351+ . set ( 'origin' , origin )
352+ . set ( 'authorization' , `Bearer ${ apiKey } ` )
353+ . set ( 'content-type' , 'application/vnd.yjs.doc' )
354+ . set ( 'accept' , 'text/markdown' )
355+ . send ( Buffer . from ( yjsUpdate ) ) ;
356+
357+ expect ( response . status ) . toBe ( 200 ) ;
358+ expect ( response . text ) . toContain ( '⚠️' ) ;
359+ expect ( response . text ) . toContain ( 'Be careful' ) ;
360+ } ) ;
361+
362+ test ( 'POST /api/convert Yjs to Markdown preserves interlinking link' , async ( ) => {
363+ const app = initApp ( ) ;
364+ const editor = ServerBlockNoteEditor . create ( { schema : docsBlockNoteSchema } ) ;
365+ const blocks = [
366+ {
367+ type : 'paragraph' as const ,
368+ content : [
369+ {
370+ type : 'interlinkingLinkInline' as const ,
371+ props : {
372+ docId : '00000000-0000-0000-0000-000000000123' ,
373+ title : 'Other doc' ,
374+ disabled : false ,
375+ trigger : '/' as const ,
376+ } ,
377+ } ,
378+ ] ,
379+ } ,
380+ ] ;
381+ const yDocument = editor . blocksToYDoc ( blocks , 'document-store' ) ;
382+ const yjsUpdate = Y . encodeStateAsUpdate ( yDocument ) ;
383+ const response = await request ( app )
384+ . post ( '/api/convert' )
385+ . set ( 'origin' , origin )
386+ . set ( 'authorization' , `Bearer ${ apiKey } ` )
387+ . set ( 'content-type' , 'application/vnd.yjs.doc' )
388+ . set ( 'accept' , 'text/markdown' )
389+ . send ( Buffer . from ( yjsUpdate ) ) ;
390+
391+ expect ( response . status ) . toBe ( 200 ) ;
392+ expect ( response . text ) . toContain (
393+ '[Other doc](/docs/00000000-0000-0000-0000-000000000123/' ,
394+ ) ;
395+ } ) ;
396+
397+ test ( 'POST /api/convert Yjs to HTML with PDF block' , async ( ) => {
398+ const app = initApp ( ) ;
399+ const editor = ServerBlockNoteEditor . create ( { schema : docsBlockNoteSchema } ) ;
400+ const blocks = [
401+ {
402+ type : 'pdf' as const ,
403+ props : {
404+ url : 'https://example.com/file.pdf' ,
405+ name : 'Annual report' ,
406+ showPreview : true ,
407+ } ,
408+ } ,
409+ ] ;
410+ const yDocument = editor . blocksToYDoc ( blocks , 'document-store' ) ;
411+ const yjsUpdate = Y . encodeStateAsUpdate ( yDocument ) ;
412+ const response = await request ( app )
413+ . post ( '/api/convert' )
414+ . set ( 'origin' , origin )
415+ . set ( 'authorization' , `Bearer ${ apiKey } ` )
416+ . set ( 'content-type' , 'application/vnd.yjs.doc' )
417+ . set ( 'accept' , 'text/html' )
418+ . send ( Buffer . from ( yjsUpdate ) ) ;
419+
420+ expect ( response . status ) . toBe ( 200 ) ;
421+ expect ( response . text ) . toContain ( '<iframe' ) ;
422+ expect ( response . text ) . toContain ( 'src="https://example.com/file.pdf"' ) ;
423+ expect ( response . text ) . toContain ( 'title="Annual report"' ) ;
424+ } ) ;
425+
426+ test ( 'POST /api/convert Yjs to HTML with interlinking inline content' , async ( ) => {
427+ const app = initApp ( ) ;
428+ const editor = ServerBlockNoteEditor . create ( { schema : docsBlockNoteSchema } ) ;
429+ const blocks = [
430+ {
431+ type : 'paragraph' as const ,
432+ content : [
433+ {
434+ type : 'interlinkingLinkInline' as const ,
435+ props : {
436+ docId : '00000000-0000-0000-0000-000000000123' ,
437+ title : 'Other doc' ,
438+ disabled : false ,
439+ trigger : '/' as const ,
440+ } ,
441+ } ,
442+ ] ,
443+ } ,
444+ ] ;
445+ const yDocument = editor . blocksToYDoc ( blocks , 'document-store' ) ;
446+ const yjsUpdate = Y . encodeStateAsUpdate ( yDocument ) ;
447+ const response = await request ( app )
448+ . post ( '/api/convert' )
449+ . set ( 'origin' , origin )
450+ . set ( 'authorization' , `Bearer ${ apiKey } ` )
451+ . set ( 'content-type' , 'application/vnd.yjs.doc' )
452+ . set ( 'accept' , 'text/html' )
453+ . send ( Buffer . from ( yjsUpdate ) ) ;
454+
455+ expect ( response . status ) . toBe ( 200 ) ;
456+ expect ( response . text ) . toContain (
457+ 'href="/docs/00000000-0000-0000-0000-000000000123/"' ,
458+ ) ;
459+ expect ( response . text ) . toContain (
460+ 'data-doc-id="00000000-0000-0000-0000-000000000123"' ,
461+ ) ;
462+ expect ( response . text ) . toContain ( 'title="Other doc"' ) ;
463+ expect ( response . text ) . toContain ( 'Other doc' ) ;
464+ expect ( response . text ) . not . toContain ( 'data-inline-content-type' ) ;
465+ } ) ;
466+
467+ test ( 'POST /api/convert Yjs to HTML with disabled interlinking renders no link' , async ( ) => {
468+ const app = initApp ( ) ;
469+ const editor = ServerBlockNoteEditor . create ( { schema : docsBlockNoteSchema } ) ;
470+ const blocks = [
471+ {
472+ type : 'paragraph' as const ,
473+ content : [
474+ {
475+ type : 'interlinkingLinkInline' as const ,
476+ props : {
477+ docId : '00000000-0000-0000-0000-000000000123' ,
478+ title : 'Hidden' ,
479+ disabled : true ,
480+ trigger : '/' as const ,
481+ } ,
482+ } ,
483+ ] ,
484+ } ,
485+ ] ;
486+ const yDocument = editor . blocksToYDoc ( blocks , 'document-store' ) ;
487+ const yjsUpdate = Y . encodeStateAsUpdate ( yDocument ) ;
488+ const response = await request ( app )
489+ . post ( '/api/convert' )
490+ . set ( 'origin' , origin )
491+ . set ( 'authorization' , `Bearer ${ apiKey } ` )
492+ . set ( 'content-type' , 'application/vnd.yjs.doc' )
493+ . set ( 'accept' , 'text/html' )
494+ . send ( Buffer . from ( yjsUpdate ) ) ;
495+
496+ expect ( response . status ) . toBe ( 200 ) ;
497+ expect ( response . text ) . not . toContain ( 'href=' ) ;
498+ expect ( response . text ) . not . toContain ( 'data-doc-id' ) ;
499+ expect ( response . text ) . not . toContain ( 'Hidden' ) ;
500+ } ) ;
501+
502+ test ( 'POST /api/convert Yjs to BlockNote JSON preserves pageBreak block' , async ( ) => {
503+ const app = initApp ( ) ;
504+ const editor = ServerBlockNoteEditor . create ( { schema : docsBlockNoteSchema } ) ;
505+ const blocks = [
506+ {
507+ type : 'paragraph' as const ,
508+ content : [ { type : 'text' as const , text : 'before' , styles : { } } ] ,
509+ } ,
510+ { type : 'pageBreak' as const } ,
511+ {
512+ type : 'paragraph' as const ,
513+ content : [ { type : 'text' as const , text : 'after' , styles : { } } ] ,
514+ } ,
515+ ] ;
516+ const yDocument = editor . blocksToYDoc ( blocks , 'document-store' ) ;
517+ const yjsUpdate = Y . encodeStateAsUpdate ( yDocument ) ;
518+ const response = await request ( app )
519+ . post ( '/api/convert' )
520+ . set ( 'origin' , origin )
521+ . set ( 'authorization' , `Bearer ${ apiKey } ` )
522+ . set ( 'content-type' , 'application/vnd.yjs.doc' )
523+ . set ( 'accept' , 'application/json' )
524+ . send ( Buffer . from ( yjsUpdate ) ) ;
525+
526+ expect ( response . status ) . toBe ( 200 ) ;
527+ const types = ( response . body as { type : string } [ ] ) . map ( ( b ) => b . type ) ;
528+ expect ( types ) . toContain ( 'pageBreak' ) ;
529+ } ) ;
530+
531+ test ( 'POST /api/convert Yjs to BlockNote JSON preserves uploadLoader block' , async ( ) => {
532+ const app = initApp ( ) ;
533+ const editor = ServerBlockNoteEditor . create ( { schema : docsBlockNoteSchema } ) ;
534+ const blocks = [
535+ {
536+ type : 'uploadLoader' as const ,
537+ props : {
538+ information : 'uploading' ,
539+ type : 'loading' as const ,
540+ blockUploadName : 'doc.pdf' ,
541+ } ,
542+ } ,
543+ ] ;
544+ const yDocument = editor . blocksToYDoc ( blocks , 'document-store' ) ;
545+ const yjsUpdate = Y . encodeStateAsUpdate ( yDocument ) ;
546+ const response = await request ( app )
547+ . post ( '/api/convert' )
548+ . set ( 'origin' , origin )
549+ . set ( 'authorization' , `Bearer ${ apiKey } ` )
550+ . set ( 'content-type' , 'application/vnd.yjs.doc' )
551+ . set ( 'accept' , 'application/json' )
552+ . send ( Buffer . from ( yjsUpdate ) ) ;
553+
554+ expect ( response . status ) . toBe ( 200 ) ;
555+ const types = ( response . body as { type : string } [ ] ) . map ( ( b ) => b . type ) ;
556+ expect ( types ) . toContain ( 'uploadLoader' ) ;
557+ } ) ;
558+
303559 test ( 'POST /api/convert with invalid Yjs content returns 400' , async ( ) => {
304560 const destroySpy = vi . spyOn ( Y . Doc . prototype , 'destroy' ) ;
305561 const app = initApp ( ) ;
0 commit comments