forked from elm-explorations/test
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathQuery.elm
More file actions
386 lines (289 loc) · 9.92 KB
/
Query.elm
File metadata and controls
386 lines (289 loc) · 9.92 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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
module Test.Html.Internal.ElmHtml.Query exposing
( Selector(..)
, query, queryAll, queryChildren, queryChildrenAll, queryInNode
, queryById, queryByClassName, queryByClassList, queryByStyle, queryByTagName, queryByAttribute, queryByBoolAttribute
, getChildren
)
{-| Query things using ElmHtml
@docs Selector
@docs query, queryAll, queryChildren, queryChildrenAll, queryInNode
@docs queryById, queryByClassName, queryByClassList, queryByStyle, queryByTagName, queryByAttribute, queryByBoolAttribute
@docs getChildren
-}
import Dict
import String
import Test.Html.Internal.ElmHtml.InternalTypes exposing (..)
{-| Selectors to query a Html element
- Id, classname, classlist, tag are all what you'd expect
- Attribute and bool attribute are attributes
- ConainsText just searches inside for the given text
-}
type Selector
= Id String
| ClassName String
| ClassList (List String)
| Tag String
| Attribute String String
| BoolAttribute String Bool
| Style { key : String, value : String }
| ContainsText String
| ContainsExactText String
| Multiple (List Selector)
{-| Query for a node with a given tag in a Html element
-}
queryByTagName : String -> ElmHtml msg -> List (ElmHtml msg)
queryByTagName tagname =
query (Tag tagname)
{-| Query for a node with a given id in a Html element
-}
queryById : String -> ElmHtml msg -> List (ElmHtml msg)
queryById id =
query (Id id)
{-| Query for a node with a given classname in a Html element
-}
queryByClassName : String -> ElmHtml msg -> List (ElmHtml msg)
queryByClassName classname =
query (ClassName classname)
{-| Query for a node with all the given classnames in a Html element
-}
queryByClassList : List String -> ElmHtml msg -> List (ElmHtml msg)
queryByClassList classList =
query (ClassList classList)
{-| Query for a node with the given style in a Html element
-}
queryByStyle : { key : String, value : String } -> ElmHtml msg -> List (ElmHtml msg)
queryByStyle style =
query (Style style)
{-| Query for a node with a given attribute in a Html element
-}
queryByAttribute : String -> String -> ElmHtml msg -> List (ElmHtml msg)
queryByAttribute key value =
query (Attribute key value)
{-| Query for a node with a given attribute in a Html element
-}
queryByBoolAttribute : String -> Bool -> ElmHtml msg -> List (ElmHtml msg)
queryByBoolAttribute key value =
query (BoolAttribute key value)
{-| Query an ElmHtml element using a selector, searching all children.
-}
query : Selector -> ElmHtml msg -> List (ElmHtml msg)
query selector =
queryInNode selector
{-| Query an ElmHtml node using multiple selectors, considering both the node itself
as well as all of its descendants.
-}
queryAll : List Selector -> ElmHtml msg -> List (ElmHtml msg)
queryAll selectors =
query (Multiple selectors)
{-| Query an ElmHtml node using a selector, considering both the node itself
as well as all of its descendants.
-}
queryInNode : Selector -> ElmHtml msg -> List (ElmHtml msg)
queryInNode =
queryInNodeHelp Nothing
{-| Query an ElmHtml node using a selector, considering both the node itself
as well as all of its descendants.
-}
queryChildren : Selector -> ElmHtml msg -> List (ElmHtml msg)
queryChildren =
queryInNodeHelp (Just 1)
{-| Returns just the immediate children of an ElmHtml node
-}
getChildren : ElmHtml msg -> List (ElmHtml msg)
getChildren elmHtml =
case elmHtml of
NodeEntry { children } ->
children
_ ->
[]
{-| Query to ensure an ElmHtml node has all selectors given, without considering
any descendants lower than its immediate children.
-}
queryChildrenAll : List Selector -> ElmHtml msg -> List (ElmHtml msg)
queryChildrenAll selectors =
queryInNodeHelp (Just 1) (Multiple selectors)
queryInNodeHelp : Maybe Int -> Selector -> ElmHtml msg -> List (ElmHtml msg)
queryInNodeHelp maxDescendantDepth selector node =
case node of
NodeEntry record ->
let
childEntries =
descendInQuery maxDescendantDepth selector record.children
in
if predicateFromSelector selector node then
node :: childEntries
else
childEntries
TextTag { text } ->
case selector of
ContainsText innerText ->
if String.contains innerText text then
[ node ]
else
[]
ContainsExactText innerText ->
if innerText == text then
[ node ]
else
[]
_ ->
[]
MarkdownNode _ ->
if predicateFromSelector selector node then
[ node ]
else
[]
_ ->
[]
descendInQuery : Maybe Int -> Selector -> List (ElmHtml msg) -> List (ElmHtml msg)
descendInQuery maxDescendantDepth selector children =
case maxDescendantDepth of
Nothing ->
-- No maximum, so continue.
List.concatMap
(queryInNodeHelp Nothing selector)
children
Just depth ->
if depth > 0 then
-- Continue with maximum depth reduced by 1.
List.concatMap
(queryInNodeHelp (Just (depth - 1)) selector)
children
else
[]
predicateFromSelector : Selector -> ElmHtml msg -> Bool
predicateFromSelector selector html =
case html of
NodeEntry record ->
record
|> nodeRecordPredicate selector
MarkdownNode markdownModel ->
markdownModel
|> markdownPredicate selector
_ ->
False
hasAllSelectors : List Selector -> ElmHtml msg -> Bool
hasAllSelectors selectors record =
List.map predicateFromSelector selectors
|> List.map (\selector -> selector record)
|> List.all identity
hasAttribute : String -> String -> Facts msg -> Bool
hasAttribute attribute queryString facts =
case Dict.get attribute facts.stringAttributes of
Just id ->
id == queryString
Nothing ->
False
hasBoolAttribute : String -> Bool -> Facts msg -> Bool
hasBoolAttribute attribute value facts =
case Dict.get attribute facts.boolAttributes of
Just id ->
id == value
Nothing ->
False
hasClass : String -> Facts msg -> Bool
hasClass queryString facts =
List.member queryString (classnames facts)
hasClasses : List String -> Facts msg -> Bool
hasClasses classList facts =
containsAll classList (classnames facts)
hasStyle : { key : String, value : String } -> Facts msg -> Bool
hasStyle style facts =
Dict.get style.key facts.styles == Just style.value
classnames : Facts msg -> List String
classnames facts =
(case
( Dict.get "class" facts.stringAttributes
, Dict.get "className" facts.stringAttributes
)
of
( Just _, Just _ ) ->
-- If you use both the `class` attribute and the `className` property at the same time,
-- it’s undefined which classes you end up with. It depends on which order they are specified,
-- which order elm/virtual-dom happens to apply them, and which of them changed most recently.
-- Mixing both is not a good idea.
--
-- This code should be impossible to reach because of the validation in
-- Test.Html.Internal.ElmHtml.InternalTypes.decodeOthers.
--
-- If we ever reach this code, silently claim that there are no classes (that no classes match
-- the node).
""
( Just class, Nothing ) ->
class
( Nothing, Just className ) ->
className
( Nothing, Nothing ) ->
""
)
|> String.split " "
containsAll : List a -> List a -> Bool
containsAll a b =
b
|> List.foldl (\i acc -> List.filter ((/=) i) acc) a
|> List.isEmpty
nodeRecordPredicate : Selector -> (NodeRecord msg -> Bool)
nodeRecordPredicate selector =
case selector of
Id id ->
.facts
>> hasAttribute "id" id
ClassName classname ->
.facts
>> hasClass classname
ClassList classList ->
.facts
>> hasClasses classList
Tag tag ->
.tag
>> (==) tag
Attribute key value ->
.facts
>> hasAttribute key value
BoolAttribute key value ->
.facts
>> hasBoolAttribute key value
Style style ->
.facts
>> hasStyle style
ContainsText _ ->
always False
ContainsExactText _ ->
always False
Multiple selectors ->
NodeEntry
>> hasAllSelectors selectors
markdownPredicate : Selector -> (MarkdownNodeRecord msg -> Bool)
markdownPredicate selector =
case selector of
Id id ->
.facts
>> hasAttribute "id" id
ClassName classname ->
.facts
>> hasClass classname
ClassList classList ->
.facts
>> hasClasses classList
Tag _ ->
always False
Attribute key value ->
.facts
>> hasAttribute key value
BoolAttribute key value ->
.facts
>> hasBoolAttribute key value
Style style ->
.facts
>> hasStyle style
ContainsText text ->
.model
>> .markdown
>> String.contains text
ContainsExactText text ->
.model
>> .markdown
>> (==) text
Multiple selectors ->
MarkdownNode
>> hasAllSelectors selectors