-
-
Notifications
You must be signed in to change notification settings - Fork 752
Expand file tree
/
Copy pathrichTextEditor.js
More file actions
178 lines (151 loc) · 5.87 KB
/
richTextEditor.js
File metadata and controls
178 lines (151 loc) · 5.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
import WebElement from '../../element/WebElement.js'
const MARKER = 'data-codeceptjs-rte-target'
const EDITOR = {
STANDARD: 'standard',
IFRAME: 'iframe',
CONTENTEDITABLE: 'contenteditable',
HIDDEN_TEXTAREA: 'hidden-textarea',
UNREACHABLE: 'unreachable',
}
function detectAndMark(el, opts) {
const marker = opts.marker
const kinds = opts.kinds
const CE = '[contenteditable="true"], [contenteditable=""]'
function mark(kind, target) {
document.querySelectorAll('[' + marker + ']').forEach(n => n.removeAttribute(marker))
if (target && target.nodeType === 1) target.setAttribute(marker, '1')
return kind
}
if (!el || el.nodeType !== 1) return mark(kinds.STANDARD, el)
const tag = el.tagName
if (tag === 'IFRAME') return mark(kinds.IFRAME, el)
if (el.isContentEditable) return mark(kinds.CONTENTEDITABLE, el)
const isFormHidden = tag === 'INPUT' && el.type === 'hidden'
if ((tag === 'INPUT' || tag === 'TEXTAREA') && !isFormHidden) {
const style = window.getComputedStyle(el)
if (style.display === 'none') return mark(kinds.UNREACHABLE, el)
}
const canSearchDescendants = tag !== 'INPUT' && tag !== 'TEXTAREA'
if (canSearchDescendants) {
const iframe = el.querySelector('iframe')
if (iframe) return mark(kinds.IFRAME, iframe)
const ce = el.querySelector(CE)
if (ce) return mark(kinds.CONTENTEDITABLE, ce)
const textareas = [...el.querySelectorAll('textarea')]
const focusable = textareas.find(t => window.getComputedStyle(t).display !== 'none')
const textarea = focusable || textareas[0]
if (textarea) return mark(kinds.HIDDEN_TEXTAREA, textarea)
}
return mark(kinds.STANDARD, el)
}
function detectInsideFrame() {
const MARKER = 'data-codeceptjs-rte-target'
const CE = '[contenteditable="true"], [contenteditable=""]'
const CONTENTEDITABLE = 'contenteditable'
const HIDDEN_TEXTAREA = 'hidden-textarea'
const body = document.body
document.querySelectorAll('[' + MARKER + ']').forEach(n => n.removeAttribute(MARKER))
if (body.isContentEditable) return CONTENTEDITABLE
const ce = body.querySelector(CE)
if (ce) {
ce.setAttribute(MARKER, '1')
return CONTENTEDITABLE
}
const textareas = [...body.querySelectorAll('textarea')]
const focusable = textareas.find(t => window.getComputedStyle(t).display !== 'none')
const textarea = focusable || textareas[0]
if (textarea) {
textarea.setAttribute(MARKER, '1')
return HIDDEN_TEXTAREA
}
return CONTENTEDITABLE
}
async function evaluateInFrame(helper, body, fn) {
if (body.helperType === 'webdriver') {
return helper.executeScript(fn)
}
return body.element.evaluate(fn)
}
function focusMarkedInFrameScript() {
const el = document.querySelector('[data-codeceptjs-rte-target]') || document.body
el.focus()
return document.activeElement === el
}
function selectAllInFrameScript() {
const el = document.querySelector('[data-codeceptjs-rte-target]') || document.body
el.focus()
const range = document.createRange()
range.selectNodeContents(el)
const sel = window.getSelection()
sel.removeAllRanges()
sel.addRange(range)
return document.activeElement === el
}
function selectAllInEditable(el) {
const doc = el.ownerDocument
const win = doc.defaultView
el.focus()
const range = doc.createRange()
range.selectNodeContents(el)
const sel = win.getSelection()
sel.removeAllRanges()
sel.addRange(range)
}
function unmarkAll(marker) {
document.querySelectorAll('[' + marker + ']').forEach(n => n.removeAttribute(marker))
}
function isActive(el) {
return el.ownerDocument.activeElement === el
}
async function assertFocused(target) {
const focused = await target.evaluate(isActive)
if (!focused) {
throw new Error('fillField: rich editor target did not accept focus. Locator must point at the visible editor surface (a wrapper, iframe, or contenteditable) — not a hidden backing element.')
}
}
async function findMarked(helper) {
const root = helper.page || helper.browser
const raw = await root.$('[' + MARKER + ']')
return new WebElement(raw, helper)
}
async function clearMarker(helper) {
if (helper.page) return helper.page.evaluate(unmarkAll, MARKER)
return helper.executeScript(unmarkAll, MARKER)
}
export async function fillRichEditor(helper, el, value) {
const source = el instanceof WebElement ? el : new WebElement(el, helper)
const kind = await source.evaluate(detectAndMark, { marker: MARKER, kinds: EDITOR })
if (kind === EDITOR.STANDARD) return false
if (kind === EDITOR.UNREACHABLE) {
throw new Error('fillField: cannot fill a display:none form control. Locator must point at the visible editor surface (a wrapper, iframe, or contenteditable).')
}
const target = await findMarked(helper)
const delay = helper.options.pressKeyDelay
if (kind === EDITOR.IFRAME) {
await target.inIframe(async body => {
const innerKind = await evaluateInFrame(helper, body, detectInsideFrame)
if (innerKind === EDITOR.HIDDEN_TEXTAREA) {
const focused = await evaluateInFrame(helper, body, focusMarkedInFrameScript)
if (!focused) throw new Error('fillField: rich editor target inside iframe did not accept focus.')
await body.selectAllAndDelete()
await body.typeText(value, { delay })
} else {
const focused = await evaluateInFrame(helper, body, selectAllInFrameScript)
if (!focused) throw new Error('fillField: rich editor target inside iframe did not accept focus.')
await body.typeText(value, { delay })
}
})
} else if (kind === EDITOR.HIDDEN_TEXTAREA) {
await target.focus()
await assertFocused(target)
await target.selectAllAndDelete()
await target.typeText(value, { delay })
} else if (kind === EDITOR.CONTENTEDITABLE) {
await target.click()
await target.evaluate(selectAllInEditable)
await assertFocused(target)
await target.typeText(value, { delay })
}
await clearMarker(helper)
return true
}