@@ -22,12 +22,12 @@ function makeEditor(styleDataFormat: StyleFormat): Editor {
2222 } ) ;
2323}
2424
25- /** Places the cursor inside the first data cell. */
25+ /** Places the cursor inside the paragraph of the first data cell. */
2626function placeCursorInFirstCell ( editor : Editor ) : void {
2727 let pos = 1 ;
2828 editor . state . doc . descendants ( ( node , nodePos ) => {
2929 if ( pos === 1 && node . type . name === "tableCell" ) {
30- pos = nodePos + 1 ;
30+ pos = nodePos + 2 ;
3131 }
3232 return pos === 1 ;
3333 } ) ;
@@ -87,3 +87,142 @@ describe("TableBackgroundColor NodeView — class format borders", () => {
8787 expect ( table . classList . contains ( "has-table-border" ) ) . toBe ( false ) ;
8888 } ) ;
8989} ) ;
90+
91+ const SIZED_CLASS_HTML =
92+ '<table data-width="400px" data-min-height="120px"><tbody>' +
93+ '<tr><th data-cell-width="150px" data-cell-height="40px">H</th><th>H2</th></tr>' +
94+ '<tr><td data-cell-width="150px" data-cell-height="40px">A</td><td>B</td></tr>' +
95+ "</tbody></table>" ;
96+
97+ /** Opening tags of every table, col, td and th element in the serialized HTML. */
98+ function tableTags ( html : string ) : string [ ] {
99+ return html . match ( / < ( t a b l e | c o l | t d | t h ) \b [ ^ > ] * > / g) ?? [ ] ;
100+ }
101+
102+ describe ( "Table sizes — class format" , ( ) => {
103+ let editor : Editor ;
104+ afterEach ( ( ) => editor ?. destroy ( ) ) ;
105+
106+ it ( "saves table, column and cell sizes as data attributes without any style attribute" , ( ) => {
107+ editor = makeEditor ( "class" ) ;
108+ editor . commands . setContent ( SIZED_CLASS_HTML ) ;
109+ const html = editor . getHTML ( ) ;
110+
111+ expect ( html ) . toContain ( '<table data-width="400px" data-min-height="120px">' ) ;
112+ expect ( html ) . toContain ( '<colgroup><col data-col-width="150px"><col></colgroup>' ) ;
113+ expect ( html ) . toContain ( '<th colspan="1" rowspan="1" data-cell-width="150px" data-cell-height="40px">' ) ;
114+ expect ( html ) . toContain ( '<td colspan="1" rowspan="1" data-cell-width="150px" data-cell-height="40px">' ) ;
115+ const tags = tableTags ( html ) ;
116+ expect ( tags . length ) . toBe ( 7 ) ;
117+ for ( const tag of tags ) {
118+ expect ( tag ) . not . toContain ( "style=" ) ;
119+ }
120+ } ) ;
121+
122+ it ( "does not persist the column-derived table width" , ( ) => {
123+ editor = makeEditor ( "class" ) ;
124+ editor . commands . setContent (
125+ '<table><tbody><tr><td data-cell-width="150px">A</td><td data-cell-width="100px">B</td></tr></tbody></table>'
126+ ) ;
127+ expect ( tableTags ( editor . getHTML ( ) ) [ 0 ] ) . toBe ( "<table>" ) ;
128+ } ) ;
129+
130+ it ( "round-trips sizes through getHTML/setContent" , ( ) => {
131+ editor = makeEditor ( "class" ) ;
132+ editor . commands . setContent ( SIZED_CLASS_HTML ) ;
133+ const html = editor . getHTML ( ) ;
134+
135+ editor . commands . setContent ( html ) ;
136+ expect ( editor . getHTML ( ) ) . toBe ( html ) ;
137+ const table = editor . state . doc . firstChild ! ;
138+ expect ( table . attrs . width ) . toBe ( "400px" ) ;
139+ expect ( table . attrs . minHeight ) . toBe ( "120px" ) ;
140+ expect ( table . firstChild ! . firstChild ! . attrs . cellWidth ) . toBe ( "150px" ) ;
141+ expect ( table . firstChild ! . firstChild ! . attrs . cellHeight ) . toBe ( "40px" ) ;
142+ } ) ;
143+
144+ it ( "still loads sizes from older class-format content that has inline style widths" , ( ) => {
145+ editor = makeEditor ( "class" ) ;
146+ editor . commands . setContent (
147+ '<table style="width: 400px; min-height: 120px;" data-width="400px" data-min-height="120px">' +
148+ '<colgroup><col style="width: 150px;"><col></colgroup><tbody>' +
149+ '<tr><td style="width: 150px; height: 40px;" data-cell-width="150px" data-cell-height="40px">A</td>' +
150+ '<td style="width: 80px; height: 30px;">B</td></tr>' +
151+ "</tbody></table>"
152+ ) ;
153+ const table = editor . state . doc . firstChild ! ;
154+ const row = table . firstChild ! ;
155+ expect ( table . attrs . width ) . toBe ( "400px" ) ;
156+ expect ( table . attrs . minHeight ) . toBe ( "120px" ) ;
157+ expect ( row . child ( 0 ) . attrs . cellWidth ) . toBe ( "150px" ) ;
158+ expect ( row . child ( 0 ) . attrs . cellHeight ) . toBe ( "40px" ) ;
159+ // Cells without data attributes fall back to their inline style
160+ expect ( row . child ( 1 ) . attrs . cellWidth ) . toBe ( "80px" ) ;
161+ expect ( row . child ( 1 ) . attrs . cellHeight ) . toBe ( "30px" ) ;
162+
163+ // Saving again moves everything to data attributes
164+ const html = editor . getHTML ( ) ;
165+ expect ( html ) . toContain ( '<col data-col-width="150px"><col data-col-width="80px">' ) ;
166+ expect ( html ) . toContain ( 'data-cell-width="80px" data-cell-height="30px"' ) ;
167+ expect ( html ) . not . toContain ( "style=" ) ;
168+ } ) ;
169+
170+ it ( "renders and updates sizes without ever setting a style attribute" , ( ) => {
171+ const setAttribute = jest . spyOn ( Element . prototype , "setAttribute" ) ;
172+ try {
173+ editor = makeEditor ( "class" ) ;
174+ editor . commands . setContent ( SIZED_CLASS_HTML ) ;
175+ placeCursorInFirstCell ( editor ) ;
176+ editor . commands . setTableWidth ( "500px" ) ;
177+ editor . commands . setTableMinHeight ( "200px" ) ;
178+ editor . commands . setCellAttribute ( "cellWidth" , "220px" ) ;
179+ editor . commands . setCellAttribute ( "cellHeight" , "60px" ) ;
180+ const html = editor . getHTML ( ) ;
181+
182+ expect ( html ) . toContain ( '<table data-width="500px" data-min-height="200px">' ) ;
183+ expect ( html ) . toContain ( 'data-cell-width="220px" data-cell-height="60px"' ) ;
184+ expect ( setAttribute . mock . calls . length ) . toBeGreaterThan ( 0 ) ;
185+ expect ( setAttribute . mock . calls . filter ( ( [ name ] ) => name . toLowerCase ( ) === "style" ) ) . toEqual ( [ ] ) ;
186+ } finally {
187+ setAttribute . mockRestore ( ) ;
188+ }
189+ } ) ;
190+ } ) ;
191+
192+ describe ( "Table sizes — inline format" , ( ) => {
193+ let editor : Editor ;
194+ afterEach ( ( ) => editor ?. destroy ( ) ) ;
195+
196+ // Expected strings are the inline renderer's output before the class-format sizing change.
197+ it ( "keeps the explicit-size output unchanged" , ( ) => {
198+ editor = makeEditor ( "inline" ) ;
199+ editor . commands . setContent (
200+ '<table style="width: 400px; min-height: 120px"><tbody>' +
201+ '<tr><th style="width: 150px; height: 40px">H</th><th>H2</th></tr>' +
202+ '<tr><td style="width: 150px; height: 40px">A</td><td>B</td></tr>' +
203+ "</tbody></table>"
204+ ) ;
205+ expect ( editor . getHTML ( ) ) . toBe (
206+ '<table style="width: 400px; min-height: 120px;"><colgroup><col style="width: 150px;"><col></colgroup><tbody>' +
207+ '<tr><th colspan="1" rowspan="1" style="width: 150px; height: 40px;"><p>H</p></th><th colspan="1" rowspan="1"><p>H2</p></th></tr>' +
208+ '<tr><td colspan="1" rowspan="1" style="width: 150px; height: 40px;"><p>A</p></td><td colspan="1" rowspan="1"><p>B</p></td></tr>' +
209+ "</tbody></table><p></p>"
210+ ) ;
211+ } ) ;
212+
213+ it ( "keeps the column-derived width, colors and borders unchanged" , ( ) => {
214+ editor = makeEditor ( "inline" ) ;
215+ editor . commands . setContent (
216+ "<table><tbody><tr>" +
217+ '<td style="width: 150px; height: 40px; background-color: #00ff00; border-color: #d33115">A</td>' +
218+ '<td style="width: 30%">B</td>' +
219+ "</tr></tbody></table>"
220+ ) ;
221+ expect ( editor . getHTML ( ) ) . toBe (
222+ '<table style="min-width: 175px;"><colgroup><col style="width: 150px;"><col style="width: 30%;"></colgroup><tbody><tr>' +
223+ '<td colspan="1" rowspan="1" style="width: 150px; height: 40px; background-color: rgb(0, 255, 0); border-style: solid; border-width: 1px; border-color: rgb(211, 49, 21);"><p>A</p></td>' +
224+ '<td colspan="1" rowspan="1" style="width: 30%;"><p>B</p></td>' +
225+ "</tr></tbody></table><p></p>"
226+ ) ;
227+ } ) ;
228+ } ) ;
0 commit comments