• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ckeditor / ckeditor5-inspector / 4e8ab897-03a3-4290-a64f-2b7935e4be58

03 Oct 2024 05:19AM UTC coverage: 95.42%. Remained the same
4e8ab897-03a3-4290-a64f-2b7935e4be58

Pull #189

circleci

psmyrek
Bumped `ckeditor5-dev-*` to latest stable version.
Pull Request #189: Aligned to `ckeditor5-dev` ESM changes

522 of 560 branches covered (93.21%)

Branch coverage included in aggregate %.

1124 of 1165 relevant lines covered (96.48%)

113.15 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.04
/src/model/data/utils.js
1
/**
2
 * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3
 * For licensing, see LICENSE.md.
4
 */
5

6
import {
7
        isModelElement,
8
        isModelRoot,
9
        getModelPositionDefinition,
10
        getNodePathString
11
} from '../utils';
12

13
import { compareArrays } from '../../utils';
14

15
import {
16
        stringify,
17
        stringifyPropertyList
18
} from '../../components/utils';
19

20
const DOCS_URL_PREFIX = 'https://ckeditor.com/docs/ckeditor5/latest/api/module_engine_model_';
1✔
21
const MARKER_COLORS = [
1✔
22
        '#03a9f4', '#fb8c00', '#009688', '#e91e63', '#4caf50', '#00bcd4',
23
        '#607d8b', '#cddc39', '#9c27b0', '#f44336', '#6d4c41', '#8bc34a', '#3f51b5', '#2196f3',
24
        '#f4511e', '#673ab7', '#ffb300'
25
];
26

27
export function getEditorModelRoots( editor ) {
28
        if ( !editor ) {
296!
29
                return [];
×
30
        }
31

32
        const roots = [ ...editor.model.document.roots ];
296✔
33

34
        // Put $graveyard at the end.
35
        return roots
296✔
36
                .filter( ( { rootName } ) => rootName !== '$graveyard' )
592✔
37
                .concat( roots.filter( ( { rootName } ) => rootName === '$graveyard' ) );
592✔
38
}
39

40
export function getEditorModelRanges( editor, currentRootName ) {
41
        if ( !editor ) {
211!
42
                return [];
×
43
        }
44

45
        const ranges = [];
211✔
46
        const model = editor.model;
211✔
47

48
        for ( const range of model.document.selection.getRanges() ) {
211✔
49
                if ( range.root.rootName !== currentRootName ) {
211✔
50
                        continue;
9✔
51
                }
52

53
                ranges.push( {
202✔
54
                        type: 'selection',
55
                        start: getModelPositionDefinition( range.start ),
56
                        end: getModelPositionDefinition( range.end )
57
                } );
58
        }
59

60
        return ranges;
211✔
61
}
62

63
export function getEditorModelMarkers( editor, currentRootName ) {
64
        if ( !editor ) {
202!
65
                return [];
×
66
        }
67

68
        const markers = [];
202✔
69
        const model = editor.model;
202✔
70
        let markerCount = 0;
202✔
71

72
        for ( const marker of model.markers ) {
202✔
73
                const { name, affectsData, managedUsingOperations } = marker;
7✔
74
                const start = marker.getStart();
7✔
75
                const end = marker.getEnd();
7✔
76

77
                if ( start.root.rootName !== currentRootName ) {
7!
78
                        continue;
×
79
                }
80

81
                markers.push( {
7✔
82
                        type: 'marker',
83
                        marker,
84
                        name,
85
                        affectsData,
86
                        managedUsingOperations,
87
                        presentation: {
88
                                // When there are more markers than colors, let's start over and reuse
89
                                // the colors.
90
                                color: MARKER_COLORS[ markerCount++ % ( MARKER_COLORS.length - 1 ) ]
91
                        },
92
                        start: getModelPositionDefinition( start ),
93
                        end: getModelPositionDefinition( end )
94
                } );
95
        }
96

97
        return markers;
202✔
98
}
99

100
export function getEditorModelTreeDefinition( { currentEditor, currentRootName, ranges, markers } ) {
101
        if ( !currentEditor ) {
200!
102
                return [];
×
103
        }
104

105
        const model = currentEditor.model;
200✔
106
        const modelRoot = model.document.getRoot( currentRootName );
200✔
107

108
        return [
200✔
109
                getModelNodeDefinition( modelRoot, [ ...ranges, ...markers ] )
110
        ];
111
}
112

113
export function getEditorModelNodeDefinition( currentEditor, node ) {
114
        const definition = {
13✔
115
                editorNode: node,
116
                properties: {},
117
                attributes: {}
118
        };
119

120
        if ( isModelElement( node ) ) {
13✔
121
                if ( isModelRoot( node ) ) {
12✔
122
                        definition.type = 'RootElement';
4✔
123
                        definition.name = node.rootName;
4✔
124
                        definition.url = `${ DOCS_URL_PREFIX }rootelement-RootElement.html`;
4✔
125
                } else {
126
                        definition.type = 'Element';
8✔
127
                        definition.name = node.name;
8✔
128
                        definition.url = `${ DOCS_URL_PREFIX }element-Element.html`;
8✔
129
                }
130

131
                definition.properties = {
12✔
132
                        childCount: {
133
                                value: node.childCount
134
                        },
135
                        startOffset: {
136
                                value: node.startOffset
137
                        },
138
                        endOffset: {
139
                                value: node.endOffset
140
                        },
141
                        maxOffset: {
142
                                value: node.maxOffset
143
                        }
144
                };
145
        } else {
146
                definition.name = node.data;
1✔
147
                definition.type = 'Text';
1✔
148
                definition.url = `${ DOCS_URL_PREFIX }text-Text.html`;
1✔
149

150
                definition.properties = {
1✔
151
                        startOffset: {
152
                                value: node.startOffset
153
                        },
154
                        endOffset: {
155
                                value: node.endOffset
156
                        },
157
                        offsetSize: {
158
                                value: node.offsetSize
159
                        }
160
                };
161
        }
162

163
        definition.properties.path = { value: getNodePathString( node ) };
13✔
164

165
        getSortedNodeAttributes( node )
13✔
166
                .forEach( ( [ name, value ] ) => {
167
                        definition.attributes[ name ] = { value };
5✔
168
                } );
169

170
        definition.properties = stringifyPropertyList( definition.properties );
13✔
171
        definition.attributes = stringifyPropertyList( definition.attributes );
13✔
172

173
        for ( const attribute in definition.attributes ) {
13✔
174
                const attributePropertyDefinitions = {};
5✔
175
                const attirbuteProperties = currentEditor.model.schema.getAttributeProperties( attribute );
5✔
176

177
                for ( const name in attirbuteProperties ) {
5✔
178
                        attributePropertyDefinitions[ name ] = { value: attirbuteProperties[ name ] };
2✔
179
                }
180

181
                definition.attributes[ attribute ].subProperties = stringifyPropertyList( attributePropertyDefinitions );
5✔
182
        }
183

184
        return definition;
13✔
185
}
186

187
function getModelNodeDefinition( node, ranges ) {
188
        const nodeDefinition = {};
347✔
189
        const { startOffset, endOffset } = node;
347✔
190

191
        Object.assign( nodeDefinition, {
347✔
192
                startOffset,
193
                endOffset,
194
                node,
195
                path: node.getPath(),
196
                positionsBefore: [],
197
                positionsAfter: []
198
        } );
199

200
        if ( isModelElement( node ) ) {
347✔
201
                fillElementDefinition( nodeDefinition, ranges );
272✔
202
        } else {
203
                fillTextNodeDefinition( nodeDefinition );
75✔
204
        }
205

206
        return nodeDefinition;
347✔
207
}
208

209
function fillElementDefinition( elementDefinition, ranges ) {
210
        const element = elementDefinition.node;
272✔
211

212
        Object.assign( elementDefinition, {
272✔
213
                type: 'element',
214
                name: element.name,
215
                children: [],
216
                maxOffset: element.maxOffset,
217
                positions: []
218
        } );
219

220
        for ( const child of element.getChildren() ) {
272✔
221
                elementDefinition.children.push( getModelNodeDefinition( child, ranges ) );
147✔
222
        }
223

224
        fillElementPositions( elementDefinition, ranges );
272✔
225

226
        elementDefinition.attributes = getNodeAttributesForDefinition( element );
272✔
227
}
228

229
function fillElementPositions( elementDefinition, ranges ) {
230
        for ( const range of ranges ) {
272✔
231
                const positions = getRangePositionsInElement( elementDefinition, range );
273✔
232

233
                for ( const position of positions ) {
273✔
234
                        const offset = position.offset;
388✔
235

236
                        if ( offset === 0 ) {
388✔
237
                                const firstChild = elementDefinition.children[ 0 ];
360✔
238

239
                                if ( firstChild ) {
360✔
240
                                        firstChild.positionsBefore.push( position );
116✔
241
                                } else {
242
                                        elementDefinition.positions.push( position );
244✔
243
                                }
244
                        } else if ( offset === elementDefinition.maxOffset ) {
28✔
245
                                const lastChild = elementDefinition.children[ elementDefinition.children.length - 1 ];
9✔
246

247
                                if ( lastChild ) {
9!
248
                                        lastChild.positionsAfter.push( position );
9✔
249
                                } else {
250
                                        elementDefinition.positions.push( position );
×
251
                                }
252
                        } else {
253
                                // Go backward when looking for a child that will host the end position.
254
                                // Go forward when looking for a child that will host the start position.
255
                                //
256
                                //                <foo></foo>
257
                                //                [<bar></bar>]
258
                                //                <baz></baz>
259
                                //
260
                                // instead of
261
                                //
262
                                //                <foo></foo>[
263
                                //                <bar></bar>
264
                                //                ]<baz></baz>
265
                                //
266
                                let childIndex = position.isEnd ? 0 : elementDefinition.children.length - 1;
19✔
267
                                let child = elementDefinition.children[ childIndex ];
19✔
268

269
                                while ( child ) {
19✔
270
                                        if ( child.startOffset === offset ) {
25✔
271
                                                child.positionsBefore.push( position );
1✔
272
                                                break;
1✔
273
                                        }
274

275
                                        if ( child.endOffset === offset ) {
24✔
276
                                                const nextChild = elementDefinition.children[ childIndex + 1 ];
1✔
277
                                                const isBetweenTextAndElement = child.type === 'text' && nextChild && nextChild.type === 'element';
1✔
278
                                                const isBetweenElementAndText = child.type === 'element' && nextChild && nextChild.type === 'text';
1!
279
                                                const isBetweenTwoTexts = child.type === 'text' && nextChild && nextChild.type === 'text';
1✔
280

281
                                                // Avoid the situation where the order of positions could weird around text nodes.
282
                                                //
283
                                                //                do           <element><$text>foo<$/text><$text>[]bar<$/text></element>
284
                                                //                instead of   <element><$text>foo]<$/text><$text>[bar<$/text></element>
285
                                                //
286
                                                //                do           <element><br />[]bar<$/text></element>
287
                                                //                instead of   <element><br />[<$text>]bar<$/text></element>
288
                                                //
289
                                                //                do           <element>bar<$/text>[]<br /></element>
290
                                                //                instead of   <element><$text>bar[<$/text>]<br /></element>
291
                                                //
292
                                                if ( position.isEnd && ( isBetweenTextAndElement || isBetweenElementAndText || isBetweenTwoTexts ) ) {
1!
293
                                                        nextChild.positionsBefore.push( position );
1✔
294
                                                } else {
295
                                                        child.positionsAfter.push( position );
×
296
                                                }
297

298
                                                break;
1✔
299
                                        }
300

301
                                        if ( child.startOffset < offset && child.endOffset > offset ) {
23✔
302
                                                child.positions.push( position );
17✔
303
                                                break;
17✔
304
                                        }
305

306
                                        childIndex += position.isEnd ? 1 : -1;
6✔
307
                                        child = elementDefinition.children[ childIndex ];
6✔
308
                                }
309
                        }
310
                }
311
        }
312
}
313

314
function fillTextNodeDefinition( textNodeDefinition ) {
315
        const textNode = textNodeDefinition.node;
75✔
316

317
        Object.assign( textNodeDefinition, {
75✔
318
                type: 'text',
319
                text: textNode.data,
320
                positions: [],
321
                presentation: {
322
                        dontRenderAttributeValue: true
323
                }
324
        } );
325

326
        textNodeDefinition.attributes = getNodeAttributesForDefinition( textNode );
75✔
327
}
328

329
function getNodeAttributesForDefinition( node ) {
330
        const attrs = getSortedNodeAttributes( node )
347✔
331
                .map( ( [ name, value ] ) => {
332
                        return [ name, stringify( value, false ) ];
7✔
333
                } );
334

335
        return new Map( attrs );
347✔
336
}
337

338
function getSortedNodeAttributes( node ) {
339
        return [ ...node.getAttributes() ]
360✔
340
                .sort( ( [ nameA ], [ nameB ] ) => nameA < nameB ? -1 : 1 );
10✔
341
}
342

343
function getRangePositionsInElement( node, range ) {
344
        const nodePath = node.path;
273✔
345
        const startPath = range.start.path;
273✔
346
        const endPath = range.end.path;
273✔
347
        const positions = [];
273✔
348

349
        if ( isPathPrefixingAnother( nodePath, startPath ) ) {
273✔
350
                positions.push( {
194✔
351
                        offset: startPath[ startPath.length - 1 ],
352
                        isEnd: false,
353
                        presentation: range.presentation || null,
383✔
354
                        type: range.type,
355
                        name: range.name || null
383✔
356
                } );
357
        }
358

359
        if ( isPathPrefixingAnother( nodePath, endPath ) ) {
273✔
360
                positions.push( {
194✔
361
                        offset: endPath[ endPath.length - 1 ],
362
                        isEnd: true,
363
                        presentation: range.presentation || null,
383✔
364
                        type: range.type,
365
                        name: range.name || null
383✔
366
                } );
367
        }
368

369
        return positions;
273✔
370
}
371

372
function isPathPrefixingAnother( pathA, pathB ) {
373
        if ( pathA.length === pathB.length - 1 ) {
546✔
374
                const comparison = compareArrays( pathA, pathB );
390✔
375

376
                if ( comparison === 'prefix' ) {
390✔
377
                        return true;
388✔
378
                }
379
        }
380

381
        return false;
158✔
382
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc