@ -1,22 +1,27 @@
@@ -1,22 +1,27 @@
import ReactDOM from "react-dom" ;
import ExcalidrawApp from "../excalidraw-app" ;
import { render } from "../tests/test-utils" ;
import { Pointer , UI } from "../tests/helpers/ui" ;
import { render , screen } from "../tests/test-utils" ;
import { Keyboard , Pointer , UI } from "../tests/helpers/ui" ;
import { KEYS } from "../keys" ;
import { fireEvent } from "../tests/test-utils" ;
import { BOUND_TEXT_PADDING , FONT_FAMILY } from "../constants" ;
import { ExcalidrawTextElementWithContainer } from "./types" ;
import * as textElementUtils from "./textElement" ;
// Unmount ReactDOM from root
ReactDOM . unmountComponentAtNode ( document . getElementById ( "root" ) ! ) ;
const tab = " " ;
const mouse = new Pointer ( "mouse" ) ;
describe ( "textWysiwyg" , ( ) = > {
describe ( "Test unbounded text" , ( ) = > {
let textarea : HTMLTextAreaElement ;
beforeEach ( async ( ) = > {
await render ( < ExcalidrawApp / > ) ;
const element = UI . createElement ( "text" ) ;
new Pointer ( " mouse" ) . clickOn ( element ) ;
mouse . clickOn ( element ) ;
textarea = document . querySelector (
".excalidraw-textEditorContainer > textarea" ,
) ! ;
@ -166,4 +171,243 @@ describe("textWysiwyg", () => {
@@ -166,4 +171,243 @@ describe("textWysiwyg", () => {
expect ( textarea . value ) . toEqual ( ` Line#1 \ nLine#2 ` ) ;
} ) ;
} ) ;
describe ( "Test bounded text" , ( ) = > {
let rectangle : any ;
const {
h ,
} : {
h : {
elements : any ;
} ;
} = window ;
const DUMMY_HEIGHT = 240 ;
const DUMMY_WIDTH = 160 ;
const APPROX_LINE_HEIGHT = 25 ;
const INITIAL_WIDTH = 10 ;
beforeAll ( ( ) = > {
jest
. spyOn ( textElementUtils , "getApproxLineHeight" )
. mockReturnValue ( APPROX_LINE_HEIGHT ) ;
} ) ;
beforeEach ( async ( ) = > {
await render ( < ExcalidrawApp / > ) ;
rectangle = UI . createElement ( "rectangle" , {
x : 10 ,
y : 20 ,
width : 90 ,
height : 75 ,
} ) ;
} ) ;
it ( "should bind text to container when double clicked on center" , async ( ) = > {
expect ( h . elements . length ) . toBe ( 1 ) ;
expect ( h . elements [ 0 ] . id ) . toBe ( rectangle . id ) ;
mouse . doubleClickAt (
rectangle . x + rectangle . width / 2 ,
rectangle . y + rectangle . height / 2 ,
) ;
expect ( h . elements . length ) . toBe ( 2 ) ;
const text = h . elements [ 1 ] as ExcalidrawTextElementWithContainer ;
expect ( text . type ) . toBe ( "text" ) ;
expect ( text . containerId ) . toBe ( rectangle . id ) ;
mouse . down ( ) ;
const editor = document . querySelector (
".excalidraw-textEditorContainer > textarea" ,
) as HTMLTextAreaElement ;
await new Promise ( ( r ) = > setTimeout ( r , 0 ) ) ;
fireEvent . change ( editor , { target : { value : "Hello World!" } } ) ;
editor . blur ( ) ;
expect ( rectangle . boundElements ) . toStrictEqual ( [
{ id : text.id , type : "text" } ,
] ) ;
} ) ;
it ( "should bind text to container when clicked on container and enter pressed" , async ( ) = > {
expect ( h . elements . length ) . toBe ( 1 ) ;
expect ( h . elements [ 0 ] . id ) . toBe ( rectangle . id ) ;
Keyboard . withModifierKeys ( { } , ( ) = > {
Keyboard . keyPress ( KEYS . ENTER ) ;
} ) ;
expect ( h . elements . length ) . toBe ( 2 ) ;
const text = h . elements [ 1 ] as ExcalidrawTextElementWithContainer ;
expect ( text . type ) . toBe ( "text" ) ;
expect ( text . containerId ) . toBe ( rectangle . id ) ;
const editor = document . querySelector (
".excalidraw-textEditorContainer > textarea" ,
) as HTMLTextAreaElement ;
await new Promise ( ( r ) = > setTimeout ( r , 0 ) ) ;
fireEvent . change ( editor , { target : { value : "Hello World!" } } ) ;
editor . blur ( ) ;
expect ( rectangle . boundElements ) . toStrictEqual ( [
{ id : text.id , type : "text" } ,
] ) ;
} ) ;
it ( "should update font family correctly on undo/redo by selecting bounded text when font family was updated" , async ( ) = > {
mouse . doubleClickAt (
rectangle . x + rectangle . width / 2 ,
rectangle . y + rectangle . height / 2 ,
) ;
mouse . down ( ) ;
const text = h . elements [ 1 ] as ExcalidrawTextElementWithContainer ;
let editor = document . querySelector (
".excalidraw-textEditorContainer > textarea" ,
) as HTMLTextAreaElement ;
await new Promise ( ( r ) = > setTimeout ( r , 0 ) ) ;
fireEvent . change ( editor , { target : { value : "Hello World!" } } ) ;
editor . blur ( ) ;
expect ( text . fontFamily ) . toEqual ( FONT_FAMILY . Virgil ) ;
UI . clickTool ( "text" ) ;
mouse . clickAt (
rectangle . x + rectangle . width / 2 ,
rectangle . y + rectangle . height / 2 ,
) ;
mouse . down ( ) ;
editor = document . querySelector (
".excalidraw-textEditorContainer > textarea" ,
) as HTMLTextAreaElement ;
editor . select ( ) ;
fireEvent . click ( screen . getByTitle ( /code/i ) ) ;
await new Promise ( ( r ) = > setTimeout ( r , 0 ) ) ;
editor . blur ( ) ;
expect ( h . elements [ 1 ] . fontFamily ) . toEqual ( FONT_FAMILY . Cascadia ) ;
//undo
Keyboard . withModifierKeys ( { ctrl : true } , ( ) = > {
Keyboard . keyPress ( KEYS . Z ) ;
} ) ;
expect ( h . elements [ 1 ] . fontFamily ) . toEqual ( FONT_FAMILY . Virgil ) ;
//redo
Keyboard . withModifierKeys ( { ctrl : true , shift : true } , ( ) = > {
Keyboard . keyPress ( KEYS . Z ) ;
} ) ;
expect ( h . elements [ 1 ] . fontFamily ) . toEqual ( FONT_FAMILY . Cascadia ) ;
} ) ;
it ( "should wrap text and vertcially center align once text submitted" , async ( ) = > {
jest
. spyOn ( textElementUtils , "measureText" )
. mockImplementation ( ( text , font , maxWidth ) = > {
let width = INITIAL_WIDTH ;
let height = APPROX_LINE_HEIGHT ;
let baseline = 10 ;
if ( ! text ) {
return {
width ,
height ,
baseline ,
} ;
}
baseline = 30 ;
width = DUMMY_WIDTH ;
if ( text === "Hello \nWorld!" ) {
height = APPROX_LINE_HEIGHT * 2 ;
}
if ( maxWidth ) {
width = maxWidth ;
// To capture cases where maxWidth passed is initial width
// due to which the text is not wrapped correctly
if ( maxWidth === INITIAL_WIDTH ) {
height = DUMMY_HEIGHT ;
}
}
return {
width ,
height ,
baseline ,
} ;
} ) ;
Keyboard . withModifierKeys ( { } , ( ) = > {
Keyboard . keyPress ( KEYS . ENTER ) ;
} ) ;
let text = h . elements [ 1 ] as ExcalidrawTextElementWithContainer ;
let editor = document . querySelector (
".excalidraw-textEditorContainer > textarea" ,
) as HTMLTextAreaElement ;
// mock scroll height
jest
. spyOn ( editor , "scrollHeight" , "get" )
. mockImplementation ( ( ) = > APPROX_LINE_HEIGHT * 2 ) ;
fireEvent . change ( editor , {
target : {
value : "Hello World!" ,
} ,
} ) ;
editor . dispatchEvent ( new Event ( "input" ) ) ;
await new Promise ( ( cb ) = > setTimeout ( cb , 0 ) ) ;
editor . blur ( ) ;
text = h . elements [ 1 ] as ExcalidrawTextElementWithContainer ;
expect ( text . text ) . toBe ( "Hello \nWorld!" ) ;
expect ( text . originalText ) . toBe ( "Hello World!" ) ;
expect ( text . y ) . toBe (
rectangle . y + rectangle . height / 2 - ( APPROX_LINE_HEIGHT * 2 ) / 2 ,
) ;
expect ( text . x ) . toBe ( rectangle . x + BOUND_TEXT_PADDING ) ;
expect ( text . height ) . toBe ( APPROX_LINE_HEIGHT * 2 ) ;
expect ( text . width ) . toBe ( rectangle . width - BOUND_TEXT_PADDING * 2 ) ;
// Edit and text by removing second line and it should
// still vertically align correctly
mouse . select ( rectangle ) ;
Keyboard . withModifierKeys ( { } , ( ) = > {
Keyboard . keyPress ( KEYS . ENTER ) ;
} ) ;
editor = document . querySelector (
".excalidraw-textEditorContainer > textarea" ,
) as HTMLTextAreaElement ;
fireEvent . change ( editor , {
target : {
value : "Hello" ,
} ,
} ) ;
// mock scroll height
jest
. spyOn ( editor , "scrollHeight" , "get" )
. mockImplementation ( ( ) = > APPROX_LINE_HEIGHT ) ;
editor . style . height = "25px" ;
editor . dispatchEvent ( new Event ( "input" ) ) ;
await new Promise ( ( r ) = > setTimeout ( r , 0 ) ) ;
editor . blur ( ) ;
text = h . elements [ 1 ] as ExcalidrawTextElementWithContainer ;
expect ( text . text ) . toBe ( "Hello" ) ;
expect ( text . originalText ) . toBe ( "Hello" ) ;
expect ( text . y ) . toBe (
rectangle . y + rectangle . height / 2 - APPROX_LINE_HEIGHT / 2 ,
) ;
expect ( text . x ) . toBe ( rectangle . x + BOUND_TEXT_PADDING ) ;
expect ( text . height ) . toBe ( APPROX_LINE_HEIGHT ) ;
expect ( text . width ) . toBe ( rectangle . width - BOUND_TEXT_PADDING * 2 ) ;
} ) ;
} ) ;
} ) ;