11import { v4 as uuidv4 } from 'uuid' ;
22import { AuthTestUtils } from '../../../support/auth-utils' ;
3- import { EditorSelectors , waitForReactUpdate , SlashCommandSelectors , AddPageSelectors } from '../../../support/selectors' ;
3+ import { EditorSelectors , waitForReactUpdate , AddPageSelectors } from '../../../support/selectors' ;
44
55describe ( 'Copy Image Test' , ( ) => {
66 const authUtils = new AuthTestUtils ( ) ;
77 const testEmail = `${ uuidv4 ( ) } @appflowy.io` ;
88
99 beforeEach ( ( ) => {
1010 cy . on ( 'uncaught:exception' , ( ) => false ) ;
11-
11+
1212 // Mock the image fetch
1313 cy . intercept ( 'GET' , '**/logo.png' , {
1414 statusCode : 200 ,
@@ -18,91 +18,95 @@ describe('Copy Image Test', () => {
1818 } ,
1919 } ) . as ( 'getImage' ) ;
2020
21- // We need to mock the clipboard write
22- cy . window ( ) . then ( ( win ) => {
23- // Check if clipboard exists
24- if ( win . navigator . clipboard ) {
25- cy . stub ( win . navigator . clipboard , 'write' ) . as ( 'clipboardWrite' ) ;
26- } else {
27- // Mock clipboard if it doesn't exist or is not writable directly
28- // In some browsers, we might need to redefine the property
29- const clipboardMock = {
30- write : cy . stub ( ) . as ( 'clipboardWrite' )
31- } ;
32- try {
33- // @ts -ignore
34- win . navigator . clipboard = clipboardMock ;
35- } catch ( e ) {
36- Object . defineProperty ( win . navigator , 'clipboard' , {
37- value : clipboardMock ,
38- configurable : true ,
39- writable : true
40- } ) ;
41- }
42- }
43- } ) ;
44-
4521 cy . visit ( '/login' , { failOnStatusCode : false } ) ;
4622 authUtils . signInWithTestUrl ( testEmail ) . then ( ( ) => {
4723 cy . url ( { timeout : 30000 } ) . should ( 'include' , '/app' ) ;
4824 waitForReactUpdate ( 1000 ) ;
25+
26+ // Mock the clipboard write AFTER navigation to /app
27+ cy . window ( ) . then ( ( win ) => {
28+ // Stub the clipboard.write to capture what's being written
29+ const writeStub = cy . stub ( ) . as ( 'clipboardWrite' ) . resolves ( ) ;
30+ if ( win . navigator . clipboard ) {
31+ cy . stub ( win . navigator . clipboard , 'write' ) . callsFake ( writeStub ) ;
32+ } else {
33+ Object . defineProperty ( win . navigator , 'clipboard' , {
34+ value : { write : writeStub } ,
35+ configurable : true ,
36+ writable : true
37+ } ) ;
38+ }
39+ } ) ;
4940 } ) ;
5041 } ) ;
5142
5243 it ( 'should copy image to clipboard when clicking copy button' , ( ) => {
53- // Create a new page
54- AddPageSelectors . inlineAddButton ( ) . first ( ) . click ( ) ;
55- waitForReactUpdate ( 500 ) ;
56- cy . get ( '[role="menuitem"]' ) . first ( ) . click ( ) ; // Create Doc
57- waitForReactUpdate ( 1000 ) ;
58-
59- // Focus editor
60- EditorSelectors . firstEditor ( ) . should ( 'exist' ) . click ( { force : true } ) ;
61- waitForReactUpdate ( 1000 ) ;
62-
63- // Ensure focus
64- EditorSelectors . firstEditor ( ) . focus ( ) ;
65- waitForReactUpdate ( 500 ) ;
66-
67- // Type '/' to open slash menu
68- EditorSelectors . firstEditor ( ) . type ( '/' , { force : true } ) ;
69- waitForReactUpdate ( 1000 ) ;
70-
71- // Check if slash panel exists
72- cy . get ( '[data-testid="slash-panel"]' ) . should ( 'exist' ) . should ( 'be.visible' ) ;
73-
74- // Type 'image' to filter
75- EditorSelectors . firstEditor ( ) . type ( 'image' , { force : true } ) ;
76- waitForReactUpdate ( 1000 ) ;
77-
78- // Click Image item
79- cy . get ( '[data-testid^="slash-menu-"]' ) . contains ( / ^ I m a g e $ / ) . click ( { force : true } ) ;
80- waitForReactUpdate ( 1000 ) ;
81-
82- // Upload image directly
83- cy . get ( 'input[type="file"]' ) . attachFile ( 'appflowy.png' ) ;
84- waitForReactUpdate ( 2000 ) ;
85-
86- waitForReactUpdate ( 2000 ) ;
87-
88- // The image should now be rendered.
89- // We need to hover or click it to see the toolbar.
90- // The toolbar is only visible when the block is selected/focused or hovered.
91- // ImageToolbar.tsx uses useSlateStatic, suggesting it's part of the slate render.
92-
93- // Find the image block.
94- cy . get ( '[data-block-type="image"]' ) . first ( ) . should ( 'exist' ) . trigger ( 'mouseover' , { force : true } ) . click ( { force : true } ) ;
95- waitForReactUpdate ( 1000 ) ;
96-
97- // Click the copy button
98- cy . get ( '[data-testid="copy-image-button"]' ) . should ( 'exist' ) . click ( { force : true } ) ;
99-
100- // Verify clipboard write
101- cy . get ( '@clipboardWrite' ) . should ( 'have.been.called' ) ;
102- cy . get ( '@clipboardWrite' ) . should ( ( stub : any ) => {
103- const clipboardItem = stub . args [ 0 ] [ 0 ] [ 0 ] ;
104- expect ( clipboardItem ) . to . be . instanceOf ( ClipboardItem ) ;
105- expect ( clipboardItem . types ) . to . include ( 'image/png' ) ;
106- } ) ;
44+ // Create a new page
45+ AddPageSelectors . inlineAddButton ( ) . first ( ) . click ( ) ;
46+ waitForReactUpdate ( 500 ) ;
47+ cy . get ( '[role="menuitem"]' ) . first ( ) . click ( ) ; // Create Doc
48+ waitForReactUpdate ( 1000 ) ;
49+
50+ // Close the modal that opens after creating a page
51+ cy . get ( '[role="dialog"]' ) . should ( 'exist' ) ;
52+ cy . get ( '[role="dialog"]' ) . find ( 'button' ) . filter ( ':visible' ) . last ( ) . click ( { force : true } ) ;
53+ waitForReactUpdate ( 1000 ) ;
54+
55+ // Focus editor
56+ EditorSelectors . firstEditor ( ) . should ( 'exist' ) . click ( { force : true } ) ;
57+ waitForReactUpdate ( 1000 ) ;
58+
59+ // Ensure focus
60+ EditorSelectors . firstEditor ( ) . focus ( ) ;
61+ waitForReactUpdate ( 500 ) ;
62+
63+ // Type '/' to open slash menu
64+ EditorSelectors . firstEditor ( ) . type ( '/' , { force : true } ) ;
65+ waitForReactUpdate ( 1000 ) ;
66+
67+ // Check if slash panel exists
68+ cy . get ( '[data-testid="slash-panel"]' ) . should ( 'exist' ) . should ( 'be.visible' ) ;
69+
70+ // Type 'image' to filter
71+ EditorSelectors . firstEditor ( ) . type ( 'image' , { force : true } ) ;
72+ waitForReactUpdate ( 1000 ) ;
73+
74+ // Click Image item
75+ cy . get ( '[data-testid^="slash-menu-"]' ) . contains ( / ^ I m a g e $ / ) . click ( { force : true } ) ;
76+ waitForReactUpdate ( 1000 ) ;
77+
78+ // Upload image directly
79+ cy . get ( 'input[type=\"file\"]' ) . attachFile ( 'appflowy.png' ) ;
80+ waitForReactUpdate ( 2000 ) ;
81+
82+ waitForReactUpdate ( 2000 ) ;
83+
84+ // Verify we have at least 1 image block
85+ cy . get ( '[data-block-type="image"]' ) . should ( 'have.length.at.least' , 1 ) ;
86+
87+ // Find the image block and hover to show toolbar
88+ cy . get ( '[data-block-type="image"]' ) . first ( ) . should ( 'exist' ) ;
89+ // Use realHover from cypress-real-events for proper mouse enter behavior
90+ cy . get ( '[data-block-type="image"]' ) . first ( ) . realHover ( ) ;
91+ waitForReactUpdate ( 1000 ) ;
92+
93+ // Click the copy button
94+ cy . get ( '[data-testid="copy-image-button"]' ) . should ( 'exist' ) . click ( { force : true } ) ;
95+ waitForReactUpdate ( 1000 ) ;
96+
97+ // Verify clipboard write was called with image data
98+ cy . get ( '@clipboardWrite' ) . should ( 'have.been.called' ) ;
99+ cy . get ( '@clipboardWrite' ) . then ( ( stub : any ) => {
100+ // The clipboard.write is called with an array of ClipboardItems
101+ // Each ClipboardItem has a types property
102+ expect ( stub . called ) . to . be . true ;
103+ const args = stub . args [ 0 ] ;
104+ expect ( args ) . to . have . length ( 1 ) ;
105+ const clipboardItems = args [ 0 ] ;
106+ expect ( clipboardItems ) . to . have . length ( 1 ) ;
107+ const clipboardItem = clipboardItems [ 0 ] ;
108+ // Verify it's writing image/png
109+ expect ( clipboardItem . types ) . to . include ( 'image/png' ) ;
110+ } ) ;
107111 } ) ;
108112} ) ;
0 commit comments