import { ReactSketchCanvasProps } from '../../src' ; let defaultProps ; let backgroundImagePatternId ; let backgroundFill ; let firstStrokeGroupId ; let eraserStrokeGroupId ; let firstCircleId ; let canvasBackgroundId ; let firstEraserCircleId ; const exportedSvgId = '#exported-svg' ; const exportedImageId = '#exported-image' ; const sketchingTimeId = '#sketchingTime' ; before ( ( ) => { cy . fixture ( 'props.json' ) . then ( ( props ) => { defaultProps = props ; canvasBackgroundId = `#${ props . id }__canvas-background` ; backgroundImagePatternId = `pattern#${ props . id }__background image` ; backgroundFill = `url(#${ props . id }__background)` ; firstStrokeGroupId = `#${ props . id }__stroke-group-0` ; eraserStrokeGroupId = `#${ props . id }__eraser-stroke-group` ; firstCircleId = `circle#${ props . id }__0` ; firstEraserCircleId = `circle#${ props . id }__eraser-0` ; } ) ; } ) ; beforeEach ( ( ) => { cy . visit ( '/' ) ; } ) ; it ( 'should update width on props change' , ( ) => { const updatedWidth = '100px' ; cy . getCanvas ( ) . should ( 'have.attr' , 'style' ) . and ( 'include' , `width: ${ defaultProps . width }` ) ; cy . findByRole ( 'textbox' , { name : /width/i } ) . clear ( ) . type ( updatedWidth ) ; cy . getCanvas ( ) . should ( 'have.attr' , 'style' ) . and ( 'include' , `width: ${ updatedWidth }` ) ; } ) ; it ( 'should update height on props change' , ( ) => { const updatedHeight = '200px' ; cy . getCanvas ( ) . should ( 'have.attr' , 'style' ) . and ( 'include' , `height: ${ defaultProps . height }` ) ; cy . findByRole ( 'textbox' , { name : /height/i } ) . clear ( ) . type ( updatedHeight ) ; cy . getCanvas ( ) . should ( 'have.attr' , 'style' ) . and ( 'include' , `height: ${ updatedHeight }` ) ; } ) ; it ( 'should update className on props change' , ( ) => { const updatedClassName = 'svg-canvas' ; cy . getCanvas ( ) . should ( 'have.class' , defaultProps . className ) ; cy . findByRole ( 'textbox' , { name : /className/i } ) . clear ( ) . type ( updatedClassName ) ; cy . getCanvas ( ) . should ( 'have.class' , updatedClassName ) ; } ) ; it ( 'should update backgroundImage on props change' , ( ) => { const updatedBackgroundImage = 'https://i.imgur.com/jx47T07.jpeg' ; cy . get ( canvasBackgroundId ) . should ( 'have.attr' , 'fill' , backgroundFill ) ; cy . get ( backgroundImagePatternId ) . as ( 'backgroundImage' ) . should ( 'have.attr' , 'xlink:href' , defaultProps . backgroundImage ) ; cy . findByRole ( 'textbox' , { name : 'backgroundImage' , exact : true } ) . clear ( ) . type ( updatedBackgroundImage ) ; cy . get ( canvasBackgroundId ) . should ( 'have.attr' , 'fill' , backgroundFill ) ; cy . get ( '@backgroundImage' ) . should ( 'have.attr' , 'xlink:href' , updatedBackgroundImage ) ; } ) ; it ( 'should update preserveAspectRatio of the background image' , ( ) => { const updatedPreserveAspectRatio = 'xMidYMid meet' ; cy . get ( backgroundImagePatternId ) . as ( 'backgroundImage' ) . should ( 'have.attr' , 'preserveAspectRatio' , defaultProps . preserveBackgroundImageAspectRatio ) ; cy . findByRole ( 'textbox' , { name : /preserveBackgroundImageAspectRatio/i , } ) . clear ( ) . type ( updatedPreserveAspectRatio ) ; cy . get ( '@backgroundImage' ) . should ( 'have.attr' , 'preserveAspectRatio' , updatedPreserveAspectRatio ) ; } ) ; it ( 'should change stroke width' , ( ) => { const updatedStrokeWidth = '8' ; cy . drawLine ( 100 , 100 , 100 ) ; cy . get ( firstStrokeGroupId ) . find ( 'path' ) . first ( ) . should ( 'have.attr' , 'stroke-width' , defaultProps . strokeWidth . toString ( ) ) ; cy . findByRole ( 'spinbutton' , { name : /strokeWidth/i } ) . clear ( ) . type ( updatedStrokeWidth ) ; cy . drawLine ( 50 , 50 , 100 ) ; cy . get ( firstStrokeGroupId ) . find ( 'path' ) . last ( ) . should ( 'have.attr' , 'stroke-width' , updatedStrokeWidth ) ; } ) ; it ( 'should change eraser width' , ( ) => { const updatedEraserWidth = '8' ; cy . findByRole ( 'button' , { name : /eraser/i } ) . click ( ) ; cy . drawLine ( 100 , 100 , 100 ) ; cy . get ( eraserStrokeGroupId ) . find ( 'path' ) . first ( ) . should ( 'have.attr' , 'stroke-width' , defaultProps . eraserWidth . toString ( ) ) ; cy . findByRole ( 'spinbutton' , { name : /eraserWidth/i } ) . clear ( ) . type ( updatedEraserWidth ) ; cy . drawLine ( 50 , 50 , 100 ) ; cy . get ( eraserStrokeGroupId ) . find ( 'path' ) . last ( ) . should ( 'have.attr' , 'stroke-width' , updatedEraserWidth ) ; } ) ; it ( 'should change stroke color' , ( ) => { const updatedStrokeColor = '#FF0000' ; cy . drawLine ( 100 , 100 , 100 ) ; cy . get ( firstStrokeGroupId ) . find ( 'path' ) . first ( ) . should ( 'have.attr' , 'stroke' , defaultProps . strokeColor ) ; cy . findByLabelText ( /strokeColor/i ) . focus ( ) . invoke ( 'val' , updatedStrokeColor ) . trigger ( 'change' ) ; cy . drawLine ( 50 , 50 , 100 ) ; cy . get ( firstStrokeGroupId ) . find ( 'path' ) . last ( ) . should ( 'have.attr' , 'stroke' , updatedStrokeColor . toLowerCase ( ) ) ; } ) ; it ( 'should change canvas color' , ( ) => { cy . get ( canvasBackgroundId ) . should ( 'have.attr' , 'fill' , backgroundFill ) ; const updatedCanvasColor = '#FF0000' ; cy . findByLabelText ( /canvasColor/i ) . focus ( ) . invoke ( 'val' , updatedCanvasColor ) . trigger ( 'change' ) ; cy . get ( canvasBackgroundId ) . should ( 'have.attr' , 'fill' , updatedCanvasColor . toLowerCase ( ) ) ; } ) ; describe ( 'exportWithBackgroundImage' , ( ) => { it ( 'should export svg with background when enabled and canvas color background when disabled' , ( ) => { cy . findByRole ( 'button' , { name : /export svg/i } ) . click ( ) ; cy . get ( `${ exportedSvgId } ${ canvasBackgroundId }` ) . as ( 'exportedCanvasBackground' ) . should ( 'have.attr' , 'fill' , backgroundFill ) ; cy . get ( `${ exportedSvgId } ${ backgroundImagePatternId }` ) . should ( 'have.attr' , 'xlink:href' , defaultProps . backgroundImage ) ; cy . findByRole ( 'switch' , { name : /exportWithBackgroundImage/i } ) . click ( ) ; cy . findByRole ( 'button' , { name : /export svg/i } ) . click ( ) ; cy . get ( '@exportedCanvasBackground' ) . should ( 'have.attr' , 'fill' , defaultProps . canvasColor ) ; } ) ; it ( 'should export png with background when enabled and canvas color background when disabled' , ( ) => { cy . findByRole ( 'button' , { name : /export image/i } ) . click ( ) ; cy . get ( exportedImageId ) . should ( 'have.attr' , 'src' ) . and ( 'match' , /^data:image\/png;base64/i ) . convertDataURIToKiloBytes ( ) . as ( 'fileSizeWithExportedImage' ) ; cy . findByRole ( 'switch' , { name : /exportWithBackgroundImage/i } ) . click ( ) ; cy . findByRole ( 'button' , { name : /export image/i } ) . click ( ) ; cy . get ( exportedImageId ) . should ( 'have.attr' , 'src' ) . and ( 'match' , /^data:image\/png;base64/i ) . convertDataURIToKiloBytes ( ) . then ( ( fileSizeWithoutExportedImage ) => { cy . get ( '@fileSizeWithExportedImage' ) . should ( 'not.be.lessThan' , fileSizeWithoutExportedImage ) ; } ) ; } ) ; it ( 'should export jpeg with background when enabled and canvas color background when disabled' , ( ) => { cy . findByRole ( 'radio' , { name : /jpeg/i } ) . click ( ) ; cy . findByRole ( 'button' , { name : /export image/i } ) . click ( ) ; cy . get ( exportedImageId ) . should ( 'have.attr' , 'src' ) . and ( 'match' , /^data:image\/jpeg;base64/i ) . convertDataURIToKiloBytes ( ) . as ( 'fileSizeWithExportedImage' ) ; cy . findByRole ( 'switch' , { name : /exportWithBackgroundImage/i } ) . click ( ) ; cy . findByRole ( 'button' , { name : /export image/i } ) . click ( ) ; cy . get ( exportedImageId ) . should ( 'have.attr' , 'src' ) . and ( 'match' , /^data:image\/jpeg;base64/i ) . convertDataURIToKiloBytes ( ) . then ( ( fileSizeWithoutExportedImage ) => { cy . get ( '@fileSizeWithExportedImage' ) . should ( 'not.be.lessThan' , fileSizeWithoutExportedImage ) ; } ) ; } ) ; } ) ; it ( 'should throw exception when attempted to get sketching time when withTimestamp is disabled' , ( ) => { const getSketchingTimeInString = ( sketchingTime ) : string => `${ ( sketchingTime / 1000 ) . toFixed ( 3 ) } sec` ; const initialTime = 0 ; cy . get ( sketchingTimeId ) . as ( 'sketchingTimeContainer' ) . should ( 'contain.text' , getSketchingTimeInString ( initialTime ) ) ; cy . drawSquare ( 100 ) ; cy . findByRole ( 'button' , { name : /get sketching time/i } ) . click ( ) ; cy . get ( '@sketchingTimeContainer' ) . then ( ( $sketchingTimeContainer ) => { const sketchingTime = Number ( $sketchingTimeContainer . text ( ) . slice ( 0 , 5 ) ) ; expect ( sketchingTime ) . to . be . greaterThan ( 0 ) ; } ) ; cy . findByRole ( 'switch' , { name : /withTimestamp/i } ) . click ( ) ; cy . drawSquare ( 100 , 200 , 200 ) ; cy . findByRole ( 'button' , { name : /get sketching time/i } ) . click ( ) ; cy . get ( '@sketchingTimeContainer' ) . should ( 'contain.text' , getSketchingTimeInString ( initialTime ) ) ; } ) ; describe ( 'allowOnlyPointerType' , ( ) => { it ( 'should allow sketching with mouse, touch, and stylus when allowOnlyPointerType is set as all' , ( ) => { cy . drawLine ( 50 , 0 , 10 , 'mouse' ) ; cy . drawLine ( 100 , 50 , 10 , 'touch' ) ; cy . drawLine ( 200 , 100 , 10 , 'pen' ) ; cy . get ( firstStrokeGroupId ) . find ( 'path' ) . should ( 'have.length' , 3 ) ; } ) ; it ( 'should allow sketching only with mouse when allowOnlyPointerType is set as mouse' , ( ) => { cy . findByRole ( 'radio' , { name : /mouse/i } ) . click ( ) ; cy . drawLine ( 50 , 0 , 10 , 'mouse' ) ; cy . get ( firstStrokeGroupId ) . find ( 'path' ) . should ( 'have.length' , 1 ) ; cy . drawLine ( 100 , 50 , 10 , 'touch' ) ; cy . drawLine ( 200 , 100 , 10 , 'pen' ) ; cy . get ( firstStrokeGroupId ) . find ( 'path' ) . should ( 'have.length' , 1 ) ; } ) ; it ( 'should allow sketching only with touch when allowOnlyPointerType is set as touch' , ( ) => { cy . findByRole ( 'radio' , { name : /touch/i } ) . click ( ) ; cy . drawLine ( 50 , 0 , 10 , 'touch' ) ; cy . get ( firstStrokeGroupId ) . find ( 'path' ) . should ( 'have.length' , 1 ) ; cy . drawLine ( 100 , 50 , 10 , 'mouse' ) ; cy . drawLine ( 200 , 100 , 10 , 'pen' ) ; cy . get ( firstStrokeGroupId ) . find ( 'path' ) . should ( 'have.length' , 1 ) ; } ) ; it ( 'should allow sketching only with pen when allowOnlyPointerType is set as pen' , ( ) => { cy . findByRole ( 'radio' , { name : /pen/i } ) . click ( ) ; cy . drawLine ( 50 , 0 , 10 , 'pen' ) ; cy . get ( firstStrokeGroupId ) . find ( 'path' ) . should ( 'have.length' , 1 ) ; cy . drawLine ( 100 , 50 , 10 , 'mouse' ) ; cy . drawLine ( 200 , 100 , 10 , 'touch' ) ; cy . get ( firstStrokeGroupId ) . find ( 'path' ) . should ( 'have.length' , 1 ) ; } ) ; } ) ; it ( 'should call onChange when a new stroke or eraser is added' , ( ) => { cy . get ( '#paths' ) . as ( 'pathsContainer' ) . should ( 'have.text' , 'Sketch to get paths' ) ; cy . drawLine ( 50 , 0 , 10 , 'pen' ) ; cy . get ( '@pathsContainer' ) . then ( ( $pathsContainer ) => { const paths = JSON . parse ( $pathsContainer . text ( ) ) ; expect ( paths ) . to . have . length ( 1 ) ; expect ( paths . pop ( ) ) . to . have . property ( 'drawMode' , true ) ; } ) ; cy . findByRole ( 'button' , { name : /eraser/i } ) . click ( ) ; cy . drawLine ( 50 , 0 , 10 , 'pen' ) ; cy . get ( '@pathsContainer' ) . then ( ( $pathsContainer ) => { const paths = JSON . parse ( $pathsContainer . text ( ) ) ; expect ( paths ) . to . have . length ( 2 ) ; expect ( paths . pop ( ) ) . to . have . property ( 'drawMode' , false ) ; } ) ; } ) ; it ( 'should update style' , ( ) => { const updatedStyle = { border : '10px solid red' , outline : '10px solid green' , } ; cy . getCanvas ( ) . should ( 'have.attr' , 'style' ) . CssStyleToObject ( ) . and ( 'have.any.key' , Object . keys ( defaultProps . style ) . map ( Cypress . _ . kebabCase ) ) ; cy . findByRole ( 'textbox' , { name : /style/i } ) . clear ( ) . type ( JSON . stringify ( updatedStyle ) , { parseSpecialCharSequences : false , delay : 0 , } ) ; cy . getCanvas ( ) . should ( 'have.attr' , 'style' ) . CssStyleToObject ( ) . and ( 'not.have.any.keys' , Object . keys ( defaultProps . style ) . map ( Cypress . _ . kebabCase ) ) . and ( 'have.any.keys' , Object . keys ( updatedStyle ) . map ( Cypress . _ . kebabCase ) ) ; } ) ; describe ( 'onStroke' , ( ) => { it ( 'should return the last stroke' , ( ) => { cy . drawLine ( 200 , 10 , 20 , 'pen' ) ; cy . findByRole ( 'textbox' , { name : /last stroke:pen/i } ) . StringToObject ( ) . then ( ( canvaspath ) => { expect ( canvaspath ) . has . property ( 'drawMode' , true ) ; expect ( canvaspath ) . has . deep . property ( 'paths' , [ { x : 10 , y : 20 } , { x : 210 , y : 220 } , ] ) ; } ) ; cy . drawLine ( 100 , 0 , 0 , 'pen' ) ; cy . findByRole ( 'textbox' , { name : /last stroke:pen/i } ) . StringToObject ( ) . then ( ( canvaspath ) => { expect ( canvaspath ) . has . property ( 'drawMode' , true ) ; expect ( canvaspath ) . has . deep . property ( 'paths' , [ { x : 0 , y : 0 } , { x : 100 , y : 100 } , ] ) ; } ) ; } ) ; it ( 'should return the last eraser stroke' , ( ) => { cy . drawLine ( 200 , 10 , 20 , 'pen' ) ; cy . findByRole ( 'textbox' , { name : /last stroke:pen/i } ) . StringToObject ( ) . then ( ( canvaspath ) => { expect ( canvaspath ) . has . property ( 'drawMode' , true ) ; expect ( canvaspath ) . has . deep . property ( 'paths' , [ { x : 10 , y : 20 } , { x : 210 , y : 220 } , ] ) ; } ) ; cy . findByRole ( 'button' , { name : /eraser/i } ) . click ( ) ; cy . drawLine ( 100 , 0 , 0 , 'pen' ) ; cy . findByRole ( 'textbox' , { name : /last stroke:eraser/i } ) . StringToObject ( ) . then ( ( canvaspath ) => { expect ( canvaspath ) . has . property ( 'drawMode' , false ) ; expect ( canvaspath ) . has . deep . property ( 'paths' , [ { x : 0 , y : 0 } , { x : 100 , y : 100 } , ] ) ; } ) ; } ) ; } ) ; describe ( 'point' , ( ) => { it ( 'should create a point with circle on single point stroke' , ( ) => { const x = 10 ; const y = 20 ; cy . drawPoint ( x , y ) ; cy . getCanvas ( ) . find ( firstCircleId ) . should ( 'have.attr' , 'r' , defaultProps . strokeWidth / 2 ) . should ( 'have.attr' , 'cx' , x ) . should ( 'have.attr' , 'cy' , y ) ; } ) ; it ( 'should create a eraser point with circle on single point erase' , ( ) => { const x = 10 ; const y = 20 ; cy . drawPoint ( x , y ) ; cy . findByRole ( 'button' , { name : /eraser/i } ) . click ( ) ; cy . drawPoint ( x , y ) ; cy . getCanvas ( ) . find ( firstEraserCircleId ) . should ( 'have.attr' , 'r' , defaultProps . eraserWidth / 2 ) . should ( 'have.attr' , 'cx' , x ) . should ( 'have.attr' , 'cy' , y ) ; } ) ; } ) ;