@@ -28,6 +28,7 @@ import {
2828 IUndoRedoService ,
2929 IUniverInstanceService ,
3030 LocaleService ,
31+ LocaleType ,
3132 LocalUndoRedoService ,
3233 ObjectMatrix ,
3334 RANGE_TYPE ,
@@ -143,8 +144,8 @@ interface IPrivateClipboardServiceAccess {
143144 _topLeftCellsMatch ( rowCount : number , colCount : number , range : { topRow : number ; leftCol : number } ) : boolean ;
144145}
145146
146- function createTestContext ( ) {
147- vi . stubGlobal ( 'navigator' , { appVersion : 'Linux' } ) ;
147+ function createTestContext ( appVersion = 'Linux' ) {
148+ vi . stubGlobal ( 'navigator' , { appVersion } ) ;
148149 const injector = new Injector ( ) ;
149150 injector . add ( [ ILogService , { useClass : DesktopLogService } ] ) ;
150151 injector . add ( [ IConfigService , { useClass : ConfigService } ] ) ;
@@ -163,6 +164,10 @@ function createTestContext() {
163164 injector . add ( [ LocaleService ] ) ;
164165 injector . add ( [ ErrorService ] ) ;
165166 injector . add ( [ ISheetClipboardService , { useClass : SheetClipboardService } ] ) ;
167+ const localeService = injector . get ( LocaleService ) ;
168+ localeService . load ( { [ LocaleType . ZH_CN ] : { } } ) ;
169+ localeService . setLocale ( LocaleType . ZH_CN ) ;
170+ localeService . setDirection ( 'ltr' ) ;
166171 const commandService = injector . get ( ICommandService ) ;
167172 commandService . registerCommand ( SetSelectionsOperation ) ;
168173 commandService . registerCommand ( SetWorksheetActiveOperation ) ;
@@ -276,6 +281,58 @@ describe('SheetClipboardService', () => {
276281 expect ( copyId && service . copyContentCache ( ) . get ( copyId ) ?. copyType ) . toBe ( COPY_TYPE . COPY ) ;
277282 } ) ;
278283
284+ it ( 'wraps merged sheet html with Excel metadata only when writing to the system clipboard' , async ( ) => {
285+ const { injector, service } = createTestContext ( ) ;
286+ selectRange ( injector , 1 , 1 , 2 , 2 ) ;
287+ service . addClipboardHook ( {
288+ id : 'merged-cell-html' ,
289+ onCopyCellStyle ( _row : number , _column : number , rowSpan ?: number , colSpan ?: number ) {
290+ return rowSpan || colSpan
291+ ? { rowspan : `${ rowSpan || 1 } ` , colspan : `${ colSpan || 1 } ` }
292+ : undefined ;
293+ } ,
294+ } as never ) ;
295+
296+ const generatedHtml = service . generateCopyContent ( 'unit-1' , 'sheet-1' , {
297+ startRow : 1 ,
298+ startColumn : 1 ,
299+ endRow : 2 ,
300+ endColumn : 2 ,
301+ } ) ?. html ;
302+
303+ expect ( generatedHtml ) . toMatch ( / ^ < g o o g l e - s h e e t s - h t m l - o r i g i n > < t a b l e / ) ;
304+ expect ( await service . copy ( ) ) . toBe ( true ) ;
305+
306+ const clipboard = injector . get ( IClipboardInterfaceService ) as unknown as TestClipboardInterfaceService ;
307+ const writtenHtml = clipboard . writes [ 0 ] . html ;
308+
309+ expect ( writtenHtml ) . toContain ( 'xmlns:o="urn:schemas-microsoft-com:office:office"' ) ;
310+ expect ( writtenHtml ) . toContain ( 'xmlns:x="urn:schemas-microsoft-com:office:excel"' ) ;
311+ expect ( writtenHtml ) . toContain ( 'xmlns="http://www.w3.org/TR/REC-html40"' ) ;
312+ expect ( writtenHtml ) . toContain ( '<meta name="ProgId" content="Excel.Sheet">' ) ;
313+ expect ( writtenHtml ) . toContain ( '<meta name="Generator" content="Univer">' ) ;
314+ expect ( writtenHtml ) . toContain ( '<!--StartFragment--><table' ) ;
315+ expect ( writtenHtml ) . toContain ( 'rowspan="2" colspan="2"' ) ;
316+ expect ( writtenHtml ) . toContain ( 'data-copy-id=' ) ;
317+ expect ( writtenHtml ) . toContain ( '</table><!--EndFragment-->' ) ;
318+ } ) ;
319+
320+ it ( 'recognizes its Excel-compatible clipboard html as internal content on Windows' , async ( ) => {
321+ const { injector, service } = createTestContext ( 'Windows' ) ;
322+ selectCell ( injector ) ;
323+ const notificationService = injector . get ( INotificationService ) ;
324+ const notificationSpy = vi . spyOn ( notificationService , 'show' ) ;
325+
326+ expect ( await service . copy ( ) ) . toBe ( true ) ;
327+
328+ const clipboard = injector . get ( IClipboardInterfaceService ) as unknown as TestClipboardInterfaceService ;
329+ const item = new MockClipboardItem ( { 'text/html' : clipboard . writes [ 0 ] . html } ) ;
330+
331+ await service . paste ( item as unknown as ClipboardItem ) ;
332+
333+ expect ( notificationSpy ) . not . toHaveBeenCalled ( ) ;
334+ } ) ;
335+
279336 it ( 'writes formula clipboard payload for copyable formula cells' , async ( ) => {
280337 const { injector, service } = createTestContext ( ) ;
281338 selectRange ( injector , 1 , 2 , 2 , 3 ) ;
@@ -338,6 +395,7 @@ describe('SheetClipboardService', () => {
338395 const copyId = clipboard . writes [ 0 ] . html . match ( / d a t a - c o p y - i d = " ( [ ^ " ] + ) " / ) ?. [ 1 ] ;
339396
340397 expect ( cut ) . toBe ( true ) ;
398+ expect ( clipboard . writes [ 0 ] . html ) . toContain ( 'xmlns:x="urn:schemas-microsoft-com:office:excel"' ) ;
341399 expect ( service . copyContentCache ( ) . get ( copyId ! ) ?. copyType ) . toBe ( COPY_TYPE . CUT ) ;
342400 } ) ;
343401
0 commit comments