Skip to content

Commit dec3ce5

Browse files
committed
improve tests and implementation of jest-dom
1 parent 781d17c commit dec3ce5

File tree

12 files changed

+277
-130
lines changed

12 files changed

+277
-130
lines changed

lib/src/main/kotlin/seleniumtestinglib/jestdom/Api.kt

Lines changed: 59 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,126 +2,150 @@ package seleniumtestinglib.jestdom
22

33
import org.openqa.selenium.By
44
import org.openqa.selenium.WebElement
5+
import org.openqa.selenium.remote.RemoteWebDriver
6+
import org.openqa.selenium.remote.RemoteWebElement
57

68
/**
79
* https://testing-library.com/docs/ecosystem-jest-dom/
810
*/
911
fun expect(element: WebElement?) = JestDomMatcher(element)
1012

13+
// TODO: test nulls
1114
class JestDomMatcher(
12-
private val webElement: WebElement?,
15+
private val element: WebElement?,
1316
private val requireTrue: Boolean = true,
1417
) {
1518

16-
val not get() = JestDomMatcher(webElement, requireTrue.not())
19+
val not get() = JestDomMatcher(element, requireTrue.not())
1720

1821
private fun assertTrue(condition: Boolean) {
1922
require(condition xor requireTrue.not())
2023
}
2124

2225
fun toBeDisabled() {
23-
assertTrue(webElement?.isEnabled == false)
26+
assertTrue(element?.isEnabled == false)
2427
}
2528

2629
fun toBeEnabled() {
27-
assertTrue(webElement?.isEnabled == true)
30+
assertTrue(element?.isEnabled == true)
2831
}
2932

3033
fun toBeEmptyDomElement() {
31-
assertTrue(webElement?.text?.isEmpty() == true)
34+
val innerHtml = element?.getAttribute("innerHTML")?.replace("<!--.*?-->".toRegex(), "")
35+
assertTrue(innerHtml?.isEmpty() == true)
3236
}
3337

3438
fun toBeInvalid() {
35-
assertTrue(
36-
webElement?.getAttribute("aria-invalid") in setOf("", "true")
37-
)
39+
JestDomMatcher(element, requireTrue.not())
3840
}
3941

4042
fun toBeInTheDocument() {
41-
requireNotNull(webElement)
43+
assertTrue(element != null)
4244
}
4345

4446
fun toBeRequired() {
4547
assertTrue(
46-
webElement?.getAttribute("required") in setOf("", "true") ||
47-
webElement?.getAttribute("aria-required") in setOf("", "true")
48+
((element?.tagName == "input") and (element?.getAttribute("type") == "file") or (element?.ariaRole?.lowercase() in setOf(
49+
"textbox",
50+
"checkbox",
51+
"radio",
52+
"email",
53+
"spinbutton",
54+
"combobox",
55+
"listbox",
56+
"date",
57+
))) and (element?.getAttribute("required") in setOf(
58+
"",
59+
"true"
60+
)) or (element?.getAttribute("aria-required") in setOf("", "true"))
4861
)
4962
}
5063

64+
private val driver get() = ((element as? RemoteWebElement)?.wrappedDriver) as? RemoteWebDriver
65+
5166
fun toBeValid() {
52-
val ariaInvalid = webElement?.getAttribute("aria-invalid")
53-
assertTrue(ariaInvalid == null || ariaInvalid == "false")
67+
assertTrue(
68+
when (element?.tagName) {
69+
"form" -> driver?.executeScript("return arguments[0].checkValidity()", element) as Boolean
70+
else -> driver?.executeScript("return arguments[0].getAttribute('aria-invalid')", element) in
71+
setOf(null, "false")
72+
}
73+
)
5474
}
5575

5676
fun toBeVisible() {
57-
assertTrue(webElement?.isDisplayed == true)
77+
assertTrue(element?.isDisplayed == true)
5878
}
5979

6080
fun toContainElement(ancestor: WebElement?) {
61-
assertTrue(webElement?.findElements(By.xpath(".//*"))?.contains(ancestor) == true)
81+
assertTrue(element?.findElements(By.xpath(".//*"))?.contains(ancestor) == true)
6282
}
6383

6484
fun toContainHtml(htmlText: String) {
65-
assertTrue(webElement?.getAttribute("innerHTML")?.contains(htmlText) == true)
85+
val normalizedHtmlText = driver?.executeScript(
86+
"""
87+
const parser = new DOMParser()
88+
const htmlDoc = parser.parseFromString(arguments[0], "text/html")
89+
return htmlDoc.querySelector("body").innerHTML
90+
""", htmlText
91+
) as String
92+
assertTrue(element?.getAttribute("innerHTML")?.contains(normalizedHtmlText) == true)
6693
}
6794

6895
fun toHaveAccessibleDescription(description: String? = null) {
69-
val accessibleDescription = webElement?.getAttribute("aria-describedby") ?: webElement?.getAttribute("title")
70-
if (description == null)
71-
assertTrue(accessibleDescription != null)
72-
else
73-
assertTrue(description == accessibleDescription)
96+
val accessibleDescription = element?.getAttribute("aria-describedby") ?: element?.getAttribute("title")
97+
if (description == null) assertTrue(accessibleDescription?.isNotBlank() == true)
98+
else assertTrue(description == accessibleDescription)
7499
}
75100

76101
fun toHaveAccessibleName() {
77-
assertTrue(webElement?.accessibleName?.isNotBlank() == true)
102+
assertTrue(element?.accessibleName?.isNotBlank() == true)
78103
}
79104

80105
fun toHaveAttribute(attribute: String, value: String) {
81-
assertTrue(value == webElement?.getAttribute(attribute))
106+
assertTrue(value == element?.getAttribute(attribute))
82107
}
83108

84109
fun toHaveClass(className: String) {
85-
assertTrue(webElement?.getAttribute("class")?.contains(className) == null)
110+
assertTrue(element?.getAttribute("class")?.contains(className) == null)
86111
}
87112

88113
fun toHaveFocus() {
89114
}
90115

91116
fun toHaveFormValues(values: Map<String, String>) {
92-
assertTrue(values.all { webElement?.getAttribute(it.key) == it.value })
117+
assertTrue(values.all { element?.getAttribute(it.key) == it.value })
93118
}
94119

95120
fun toHaveStyle(styles: Map<String, String>) {
96-
assertTrue(styles.all { webElement?.getCssValue(it.key) == it.value })
121+
assertTrue(styles.all { element?.getCssValue(it.key) == it.value })
97122
}
98123

99124
fun toHaveTextContent(text: String, normalizeWhitespace: Boolean = false) {
100125
assertTrue(
101-
text == if (normalizeWhitespace) webElement?.text?.replace(
102-
"\\s+".toRegex(),
103-
" "
104-
) else webElement?.text
126+
text == if (normalizeWhitespace) element?.text?.replace(
127+
"\\s+".toRegex(), " "
128+
) else element?.text
105129
)
106130
}
107131

108132
fun toHaveValue(value: String) {
109-
assertTrue(value == webElement?.getAttribute("value"))
133+
assertTrue(value == element?.getAttribute("value"))
110134
}
111135

112136
fun toHaveDisplayValue(value: String) {
113-
assertTrue(value == webElement?.getAttribute("value"))
137+
assertTrue(value == element?.getAttribute("value"))
114138
}
115139

116140
fun toBeChecked() {
117-
assertTrue(webElement?.getAttribute("checked") == "true")
141+
assertTrue(element?.getAttribute("checked") == "true")
118142
}
119143

120144
fun toBePartiallyChecked() {
121-
assertTrue("true" == webElement?.getAttribute("indeterminate"))
145+
assertTrue("true" == element?.getAttribute("indeterminate"))
122146
}
123147

124148
fun toHaveErrorMessage(message: String) {
125-
assertTrue(message == webElement?.getAttribute("aria-errormessage"))
149+
assertTrue(message == element?.getAttribute("aria-errormessage"))
126150
}
127151
}

lib/src/test/kotlin/seleniumtestinglib/jestdom/AccessibleDescriptionTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class AccessibleDescriptionTest(private val driver: RemoteWebDriver) {
2323

2424
expect(driver.getBy(TestId, "link")).toHaveAccessibleDescription()
2525
expect(driver.getBy(TestId, "link")).toHaveAccessibleDescription("A link to start over")
26-
// expect(driver.getBy(TestId, "extra-link")).not.toHaveAccessibleDescription()
26+
expect(driver.getBy(TestId, "extra-link")).not.toHaveAccessibleDescription()
2727
expect(driver.getBy(TestId, "link")).not.toHaveAccessibleDescription("Home page")
2828
}
2929
}

lib/src/test/kotlin/seleniumtestinglib/jestdom/ContainHtmlTest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class ContainHtmlTest(private val driver: RemoteWebDriver) {
2020
)
2121

2222
expect(driver.queryBy(TestId, "parent")).toContainHtml("""<span data-testid="child"></span>""")
23+
expect(driver.queryBy(TestId, "parent")).toContainHtml("<span data-testid='child' />")
2324
expect(driver.queryBy(TestId, "parent")).not.toContainHtml("<br />")
2425
}
2526
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package seleniumtestinglib.jestdom
2+
3+
import org.junit.jupiter.api.extension.ExtendWith
4+
import org.junit.jupiter.params.ParameterizedTest
5+
import org.junit.jupiter.params.provider.ValueSource
6+
import org.openqa.selenium.remote.RemoteWebDriver
7+
import seleniumtestinglib.DriverLifeCycle
8+
import seleniumtestinglib.queries.ByType.Role
9+
import seleniumtestinglib.queries.ByType.TestId
10+
import seleniumtestinglib.queries.getBy
11+
import seleniumtestinglib.render
12+
import kotlin.test.Test
13+
14+
@ExtendWith(DriverLifeCycle::class)
15+
class DisabledTest(private val driver: RemoteWebDriver) {
16+
17+
@ParameterizedTest
18+
@ValueSource(
19+
strings = [
20+
"""<button data-testid="test1" type="submit" disabled>submit</button>""",
21+
"""<fieldset disabled><input type="text" data-testid="test1" /></fieldset>""",
22+
]
23+
)
24+
fun disabled(html: String) {
25+
driver.render(html)
26+
27+
expect(driver.getBy(TestId, "test1")).toBeDisabled()
28+
}
29+
30+
@Test
31+
fun `links cant be disabled`() {
32+
driver.render("""<a href="/" disabled>link</a>""")
33+
34+
expect(driver.getBy(Role, "link")).not.toBeDisabled()
35+
}
36+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package seleniumtestinglib.jestdom
2+
3+
import org.junit.jupiter.api.extension.ExtendWith
4+
import org.junit.jupiter.params.ParameterizedTest
5+
import org.junit.jupiter.params.provider.ValueSource
6+
import org.openqa.selenium.By
7+
import org.openqa.selenium.remote.RemoteWebDriver
8+
import seleniumtestinglib.DriverLifeCycle
9+
import seleniumtestinglib.render
10+
11+
@ExtendWith(DriverLifeCycle::class)
12+
class EmptyDomElementTest(private val driver: RemoteWebDriver) {
13+
14+
@ParameterizedTest
15+
@ValueSource(
16+
strings = [
17+
"""<span data-testid="empty"></span>""",
18+
"""<span data-testid="with-comment"><!-- comment --></span>""",
19+
]
20+
)
21+
fun empty(html: String) {
22+
driver.render(html)
23+
24+
expect(driver.findElement(By.tagName("span"))).toBeEmptyDomElement()
25+
}
26+
27+
@ParameterizedTest
28+
@ValueSource(
29+
strings = [
30+
"""<span data-testid="not-empty"><span data-testid="empty"></span></span>""",
31+
"""<span data-testid="with-whitespace"> </span>""",
32+
]
33+
)
34+
fun `not empty`(html: String) {
35+
driver.render(html)
36+
37+
expect(driver.findElement(By.tagName("span"))).not.toBeEmptyDomElement()
38+
}
39+
}

lib/src/test/kotlin/seleniumtestinglib/jestdom/EmptyTest.kt

Lines changed: 0 additions & 25 deletions
This file was deleted.

lib/src/test/kotlin/seleniumtestinglib/jestdom/EnabledTest.kt

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,6 @@ import kotlin.test.Test
1111
@ExtendWith(DriverLifeCycle::class)
1212
class EnabledTest(private val driver: RemoteWebDriver) {
1313

14-
@Test
15-
fun disabled() {
16-
driver.render("<div><input type='text' disabled /></div>")
17-
18-
expect(driver.getBy(Role, "textbox")).toBeDisabled()
19-
}
20-
21-
@Test
22-
fun `not disabled`() {
23-
driver.render("<a href=\"...\" disabled>link</a>")
24-
25-
expect(driver.getBy(Role, "link")).not.toBeDisabled()
26-
}
27-
2814
@Test
2915
fun enabled() {
3016
driver.render("<div><input type='text' /></div>")

lib/src/test/kotlin/seleniumtestinglib/jestdom/InTheDocumentTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import org.openqa.selenium.remote.RemoteWebDriver
55
import seleniumtestinglib.DriverLifeCycle
66
import seleniumtestinglib.queries.ByType.TestId
77
import seleniumtestinglib.queries.getBy
8+
import seleniumtestinglib.queries.queryBy
89
import seleniumtestinglib.render
910
import kotlin.test.Test
1011

@@ -18,6 +19,6 @@ class InTheDocumentTest(private val driver: RemoteWebDriver) {
1819
)
1920

2021
expect(driver.getBy(TestId, "html-element")).toBeInTheDocument()
21-
// expect(driver.queryBy(TestId, "not-there")).not.toBeInTheDocument()
22+
expect(driver.queryBy(TestId, "not-there")).not.toBeInTheDocument()
2223
}
2324
}

0 commit comments

Comments
 (0)