@@ -743,6 +743,9 @@ describe('@payloadcms/plugin-import-export', () => {
743743 expect ( response . status ) . toBe ( 200 )
744744 expect ( response . headers . get ( 'content-type' ) ) . toMatch ( / a p p l i c a t i o n \/ j s o n / )
745745
746+ const contentDisposition = response . headers . get ( 'content-disposition' )
747+ expect ( contentDisposition ) . toContain ( '-pages.json' )
748+
746749 const data = await response . json ( )
747750
748751 expect ( Array . isArray ( data ) ) . toBe ( true )
@@ -2322,6 +2325,161 @@ describe('@payloadcms/plugin-import-export', () => {
23222325
23232326 await payload . delete ( { collection : 'pages' , id : page . id } )
23242327 } )
2328+
2329+ it ( 'should preserve Hebrew characters in CSV download via streaming endpoint' , async ( ) => {
2330+ const hebrewTitle = 'Hebrew BOM Test'
2331+ const hebrewLocalized = 'בדיקה עברית'
2332+
2333+ const page = await payload . create ( {
2334+ collection : 'pages' ,
2335+ data : {
2336+ title : hebrewTitle ,
2337+ localized : hebrewLocalized ,
2338+ _status : 'published' ,
2339+ } ,
2340+ locale : 'he' ,
2341+ } )
2342+
2343+ const response = await restClient . POST ( '/exports/download' , {
2344+ body : JSON . stringify ( {
2345+ data : {
2346+ collectionSlug : 'pages' ,
2347+ fields : [ 'id' , 'title' , 'localized' ] ,
2348+ format : 'csv' ,
2349+ locale : 'he' ,
2350+ where : { id : { equals : page . id } } ,
2351+ } ,
2352+ } ) ,
2353+ headers : { 'Content-Type' : 'application/json' } ,
2354+ } )
2355+
2356+ expect ( response . status ) . toBe ( 200 )
2357+ expect ( response . headers . get ( 'content-type' ) ) . toMatch ( / t e x t \/ c s v / )
2358+ expect ( response . headers . get ( 'content-type' ) ) . toContain ( 'charset=utf-8' )
2359+
2360+ const contentDisposition = response . headers . get ( 'content-disposition' )
2361+ expect ( contentDisposition ) . toContain ( '-pages.csv' )
2362+
2363+ const buffer = Buffer . from ( await response . arrayBuffer ( ) )
2364+
2365+ // Verify UTF-8 BOM is present
2366+ expect ( buffer [ 0 ] ) . toBe ( 0xef )
2367+ expect ( buffer [ 1 ] ) . toBe ( 0xbb )
2368+ expect ( buffer [ 2 ] ) . toBe ( 0xbf )
2369+
2370+ // Verify Hebrew text is correctly encoded in the CSV body
2371+ const content = buffer . toString ( 'utf-8' )
2372+ expect ( content ) . toContain ( hebrewLocalized )
2373+
2374+ await payload . delete ( { collection : 'pages' , id : page . id } )
2375+ } )
2376+
2377+ it ( 'should preserve Hebrew characters in job-created CSV export' , async ( ) => {
2378+ const hebrewTitle = 'Hebrew Jobs Test'
2379+ const hebrewLocalized = 'שלום עולם'
2380+
2381+ const page = await payload . create ( {
2382+ collection : 'pages' ,
2383+ data : {
2384+ title : hebrewTitle ,
2385+ localized : hebrewLocalized ,
2386+ _status : 'published' ,
2387+ } ,
2388+ locale : 'he' ,
2389+ } )
2390+
2391+ let doc = await payload . create ( {
2392+ collection : 'exports' ,
2393+ user,
2394+ data : {
2395+ collectionSlug : 'pages' ,
2396+ fields : [ 'id' , 'title' , 'localized' ] ,
2397+ format : 'csv' ,
2398+ locale : 'he' ,
2399+ where : { id : { equals : page . id } } ,
2400+ } ,
2401+ } )
2402+
2403+ await payload . jobs . run ( )
2404+
2405+ doc = await payload . findByID ( { collection : 'exports' , id : doc . id } )
2406+
2407+ // Verify filename includes collection slug and csv extension
2408+ expect ( doc . filename ) . toContain ( '-pages' )
2409+ expect ( doc . filename ) . toMatch ( / \. c s v $ / )
2410+ expect ( doc . mimeType ) . toContain ( 'charset=utf-8' )
2411+
2412+ const csvPath = path . join ( dirname , './uploads' , doc . filename as string )
2413+ const buffer = fs . readFileSync ( csvPath )
2414+
2415+ // Verify UTF-8 BOM
2416+ expect ( buffer [ 0 ] ) . toBe ( 0xef )
2417+ expect ( buffer [ 1 ] ) . toBe ( 0xbb )
2418+ expect ( buffer [ 2 ] ) . toBe ( 0xbf )
2419+
2420+ // Verify Hebrew text is readable
2421+ const content = buffer . toString ( 'utf-8' )
2422+ expect ( content ) . toContain ( hebrewLocalized )
2423+
2424+ // Verify via CSV parse
2425+ const data = await readCSV ( csvPath )
2426+ expect ( data [ 0 ] . localized ) . toBe ( hebrewLocalized )
2427+
2428+ await payload . delete ( { collection : 'pages' , id : page . id } )
2429+ } )
2430+
2431+ it ( 'should preserve Hebrew characters in hook-created CSV export (no jobs queue)' , async ( ) => {
2432+ const hebrewTitle = 'Hebrew Hooks Test'
2433+ const hebrewContent = 'טקסט בעברית'
2434+
2435+ const post = await payload . create ( {
2436+ collection : 'posts' ,
2437+ data : {
2438+ title : hebrewTitle ,
2439+ content : richTextData ,
2440+ _status : 'published' ,
2441+ } ,
2442+ } )
2443+
2444+ const doc = await payload . create ( {
2445+ collection : 'posts-export' ,
2446+ user,
2447+ data : {
2448+ collectionSlug : 'posts' ,
2449+ fields : [ 'id' , 'title' ] ,
2450+ format : 'csv' ,
2451+ where : { id : { equals : post . id } } ,
2452+ } ,
2453+ } )
2454+
2455+ const exportDoc = await payload . findByID ( {
2456+ collection : 'posts-export' ,
2457+ id : doc . id ,
2458+ } )
2459+
2460+ // Verify filename includes collection slug and csv extension
2461+ expect ( exportDoc . filename ) . toContain ( '-posts' )
2462+ expect ( exportDoc . filename ) . toMatch ( / \. c s v $ / )
2463+ expect ( exportDoc . mimeType ) . toContain ( 'charset=utf-8' )
2464+
2465+ const csvPath = path . join ( dirname , './uploads' , exportDoc . filename as string )
2466+ const buffer = fs . readFileSync ( csvPath )
2467+
2468+ // Verify UTF-8 BOM
2469+ expect ( buffer [ 0 ] ) . toBe ( 0xef )
2470+ expect ( buffer [ 1 ] ) . toBe ( 0xbb )
2471+ expect ( buffer [ 2 ] ) . toBe ( 0xbf )
2472+
2473+ // Verify Hebrew title is correctly encoded
2474+ const content = buffer . toString ( 'utf-8' )
2475+ expect ( content ) . toContain ( hebrewTitle )
2476+
2477+ // Verify via CSV parse
2478+ const data = await readCSV ( csvPath )
2479+ expect ( data [ 0 ] . title ) . toBe ( hebrewTitle )
2480+
2481+ await payload . delete ( { collection : 'posts' , id : post . id } )
2482+ } )
23252483 } )
23262484
23272485 describe ( 'fields' , ( ) => {
0 commit comments