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

worktile / slate-angular / 62ba9eda-73c7-4559-9959-adfb60d29862

03 Dec 2025 03:19AM UTC coverage: 46.809% (-0.4%) from 47.176%
62ba9eda-73c7-4559-9959-adfb60d29862

push

circleci

pubuzhixing8
fix: unit testing

369 of 997 branches covered (37.01%)

Branch coverage included in aggregate %.

1010 of 1949 relevant lines covered (51.82%)

32.48 hits per line

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

83.48
/packages/src/components/string/string-render.ts
1
import { SlateLeafContext } from '../../view/context';
2
import { SlateViewContext } from '../../view/context';
3
import { Text, Node } from 'slate';
4
import { getZeroTextNode } from '../../utils/dom';
5

6
export enum StringType {
1✔
7
    'normalString' = 'normalString',
1✔
8
    'lineBreakEmptyString' = 'lineBreakEmptyString',
1✔
9
    'normalEmptyText' = 'normalEmptyText',
1✔
10
    'compatibleString' = 'compatibleString',
1✔
11
    'voidString' = 'voidString'
1✔
12
}
13

14
export class SlateStringRender {
15
    nativeElement: HTMLElement;
16

17
    constructor(
18
        public context: SlateLeafContext,
152✔
19
        public viewContext: SlateViewContext
152✔
20
    ) {}
21

22
    type: StringType;
23

24
    // COMPAT: If the text is empty, it's because it's on the edge of an inline
25
    // node, so we render a zero-width space so that the selection can be
26
    // inserted next to it still.
27
    isEmptyText() {
28
        return this.context.leaf.text === '';
147✔
29
    }
30

31
    // COMPAT: Browsers will collapse trailing new lines at the end of blocks,
32
    // so we need to add an extra trailing new lines to prevent that.
33
    isCompatibleString() {
34
        return this.context.isLast && this.context.leaf.text.slice(-1) === '\n';
147✔
35
    }
36

37
    // COMPAT: Render text inside void nodes with a zero-width space.
38
    // So the node can contain selection but the text is not visible.
39
    isVoid() {
40
        return this.viewContext.editor.isVoid(this.context.parent);
148✔
41
    }
42

43
    get leaf(): Text {
44
        return this.context && this.context.leaf;
147✔
45
    }
46

47
    // COMPAT: If this is the last text node in an empty block, render a zero-
48
    // width space that will convert into a line break when copying and pasting
49
    // to support expected plain text.
50
    isLineBreakEmptyString() {
51
        return (
158✔
52
            this.context.leaf.text === '' &&
191✔
53
            this.context.parent.children[this.context.parent.children.length - 1] === this.context.text &&
54
            !this.viewContext.editor.isInline(this.context.parent) &&
55
            // [list-render] performance optimization: reduce the number of calls to the `Editor.string(editor, path)` method
56
            isEmpty(this.viewContext.editor, this.context.parent)
57
        );
58
    }
59

60
    createStringNode(type: StringType) {
61
        let newNativeElement: HTMLElement;
62
        switch (type) {
155!
63
            case StringType.lineBreakEmptyString:
64
                newNativeElement = createLineBreakEmptyStringDOM(this.getElementStringLength());
8✔
65
                break;
8✔
66
            case StringType.voidString:
67
            case StringType.normalEmptyText:
68
                newNativeElement = createEmptyOrVoidStringNode();
1✔
69
                break;
1✔
70
            case StringType.compatibleString:
71
                newNativeElement = createCompatibleStringNode(this.leaf.text);
×
72
                break;
×
73
            case StringType.normalString:
74
                newNativeElement = createDefaultStringNode(this.leaf.text);
146✔
75
                break;
146✔
76
            default:
77
                newNativeElement = createDefaultStringNode(this.leaf.text);
×
78
        }
79
        return newNativeElement;
155✔
80
    }
81

82
    render() {
83
        this.type = this.getType();
152✔
84
        this.nativeElement = this.createStringNode(this.type);
152✔
85
        return this.nativeElement;
152✔
86
    }
87

88
    getType() {
89
        if (this.isLineBreakEmptyString()) {
158✔
90
            return StringType.lineBreakEmptyString;
10✔
91
        }
92
        if (this.isVoid()) {
148✔
93
            return StringType.voidString;
1✔
94
        }
95
        if (this.isEmptyText()) {
147!
96
            return StringType.normalEmptyText;
×
97
        }
98
        if (this.isCompatibleString()) {
147!
99
            return StringType.compatibleString;
×
100
        }
101
        return StringType.normalString;
147✔
102
    }
103

104
    update(context: SlateLeafContext, viewContext: SlateViewContext) {
105
        this.context = context;
6✔
106
        this.viewContext = viewContext;
6✔
107
        const type = this.getType();
6✔
108
        if (type !== this.type) {
6✔
109
            const newNativeElement = this.createStringNode(type);
3✔
110
            this.nativeElement.replaceWith(newNativeElement);
3✔
111
            this.nativeElement = newNativeElement;
3✔
112
            this.type = type;
3✔
113
            return;
3✔
114
        }
115
        if (this.type === StringType.normalString) {
3✔
116
            this.nativeElement.textContent = this.leaf.text;
1✔
117
        }
118
    }
119

120
    getElementStringLength() {
121
        return Node.string(this.context.parent).length;
8✔
122
    }
123
}
124

125
export const createDefaultStringNode = (text: string) => {
1✔
126
    const stringNode = document.createElement('span');
146✔
127
    stringNode.textContent = text;
146✔
128
    stringNode.setAttribute('data-slate-string', 'true');
146✔
129
    stringNode.setAttribute('editable-text', '');
146✔
130
    return stringNode;
146✔
131
};
132

133
export const createEmptyOrVoidStringNode = () => {
1✔
134
    const stringNode = document.createElement('span');
1✔
135
    stringNode.setAttribute('data-slate-string', 'true');
1✔
136
    stringNode.setAttribute('data-slate-zero-width', 'z');
1✔
137
    stringNode.setAttribute('data-slate-length', '0');
1✔
138
    const zeroWidthSpace = getZeroTextNode();
1✔
139
    stringNode.appendChild(zeroWidthSpace);
1✔
140
    stringNode.setAttribute('editable-text', '');
1✔
141
    return stringNode;
1✔
142
};
143

144
export const createCompatibleStringNode = (text: string) => {
1✔
145
    const stringNode = document.createElement('span');
×
146
    stringNode.setAttribute('data-slate-string', 'true');
×
147
    stringNode.textContent = text;
×
148
    stringNode.setAttribute('editable-text', '');
×
149
    const zeroWidthSpan = document.createElement('span');
×
150
    const zeroWidthSpace = getZeroTextNode();
×
151
    zeroWidthSpan.setAttribute('data-slate-zero-width', '');
×
152
    zeroWidthSpan.appendChild(zeroWidthSpace);
×
153
    stringNode.appendChild(zeroWidthSpan);
×
154
    return stringNode;
×
155
};
156

157
export const createLineBreakEmptyStringDOM = (elementStringLength: number) => {
1✔
158
    const stringNode = document.createElement('span');
8✔
159
    stringNode.setAttribute('data-slate-zero-width', 'n');
8✔
160
    stringNode.setAttribute('data-slate-length', `${elementStringLength}`);
8✔
161
    const zeroWidthSpace = getZeroTextNode();
8✔
162
    stringNode.appendChild(zeroWidthSpace);
8✔
163
    const brNode = document.createElement('br');
8✔
164
    stringNode.appendChild(brNode);
8✔
165
    stringNode.setAttribute('editable-text', '');
8✔
166
    return stringNode;
8✔
167
};
168

169
/**
170
 * TODO: remove when bump slate
171
 * copy from slate
172
 * @param editor
173
 * @param element
174
 * @returns
175
 */
176
export const isEmpty = (editor, element) => {
1✔
177
    const { children } = element;
11✔
178
    const [first] = children;
11✔
179
    return children.length === 0 || (children.length === 1 && Text.isText(first) && first.text === '' && !editor.isVoid(element));
11✔
180
};
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