|
| 1 | +package com.composea11yscanner.core |
| 2 | + |
| 3 | +import com.composea11yscanner.core.model.A11yNode |
| 4 | +import com.composea11yscanner.core.model.A11yRole |
| 5 | +import com.composea11yscanner.core.model.A11ySeverity |
| 6 | +import com.composea11yscanner.core.model.Color |
| 7 | +import com.composea11yscanner.core.model.DpSize |
| 8 | +import com.composea11yscanner.core.model.Rect |
| 9 | +import com.composea11yscanner.core.model.ScanResult |
| 10 | +import com.composea11yscanner.core.rule.BaseA11yRule |
| 11 | +import com.composea11yscanner.core.rule.BaseScanRule |
| 12 | +import org.junit.Assert.assertEquals |
| 13 | +import org.junit.Assert.assertFalse |
| 14 | +import org.junit.Assert.assertNull |
| 15 | +import org.junit.Assert.assertSame |
| 16 | +import org.junit.Assert.assertTrue |
| 17 | +import org.junit.Test |
| 18 | + |
| 19 | +class CoreModelAndRuleTest { |
| 20 | + |
| 21 | + private val node = A11yNode( |
| 22 | + nodeId = "node-1", |
| 23 | + composableName = "Button", |
| 24 | + bounds = Rect(0, 0, 100, 48), |
| 25 | + contentDescription = "Submit", |
| 26 | + isTouchTarget = true, |
| 27 | + touchTargetSize = DpSize(100f, 48f), |
| 28 | + textColor = Color(0xFF000000), |
| 29 | + backgroundColors = emptyList(), |
| 30 | + isFocusable = true, |
| 31 | + isMergedDescendant = false, |
| 32 | + depth = 0, |
| 33 | + role = A11yRole.Button, |
| 34 | + ) |
| 35 | + |
| 36 | + @Test |
| 37 | + fun `base node rule delegates evaluate to check and builds issue metadata`() { |
| 38 | + val rule = object : BaseA11yRule() { |
| 39 | + override val ruleId = "test-rule" |
| 40 | + override val ruleName = "Test Rule" |
| 41 | + override val severity = A11ySeverity.Warning |
| 42 | + override val wcagReference = "WCAG test" |
| 43 | + |
| 44 | + override fun check(node: A11yNode) = issue( |
| 45 | + node = node, |
| 46 | + message = "Test message", |
| 47 | + howToFix = "Test fix", |
| 48 | + ) |
| 49 | + } |
| 50 | + |
| 51 | + val issue = rule.evaluate(node) |
| 52 | + |
| 53 | + requireNotNull(issue) |
| 54 | + assertEquals("test-rule_node-1", issue.issueId) |
| 55 | + assertEquals(A11ySeverity.Warning, issue.severity) |
| 56 | + assertEquals("test-rule", issue.ruleId) |
| 57 | + assertEquals("Test Rule", issue.ruleName) |
| 58 | + assertSame(node, issue.affectedNode) |
| 59 | + assertEquals("Test message", issue.message) |
| 60 | + assertEquals("Test fix", issue.howToFix) |
| 61 | + assertEquals("WCAG test", issue.wcagReference) |
| 62 | + } |
| 63 | + |
| 64 | + @Test |
| 65 | + fun `base scan rule evaluate is a no-op and issue helper fills metadata`() { |
| 66 | + val rule = object : BaseScanRule() { |
| 67 | + override val ruleId = "scan-rule" |
| 68 | + override val ruleName = "Scan Rule" |
| 69 | + override val severity = A11ySeverity.Error |
| 70 | + override val wcagReference = null |
| 71 | + |
| 72 | + override fun evaluateAll(nodes: List<A11yNode>) = nodes.map { |
| 73 | + issue( |
| 74 | + node = it, |
| 75 | + message = "Scan message", |
| 76 | + howToFix = "Scan fix", |
| 77 | + ) |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + assertNull(rule.evaluate(node)) |
| 82 | + |
| 83 | + val issue = rule.evaluateAll(listOf(node)).single() |
| 84 | + assertEquals("scan-rule_node-1", issue.issueId) |
| 85 | + assertEquals(A11ySeverity.Error, issue.severity) |
| 86 | + assertEquals("scan-rule", issue.ruleId) |
| 87 | + assertEquals("Scan Rule", issue.ruleName) |
| 88 | + assertSame(node, issue.affectedNode) |
| 89 | + assertEquals("Scan message", issue.message) |
| 90 | + assertEquals("Scan fix", issue.howToFix) |
| 91 | + assertNull(issue.wcagReference) |
| 92 | + } |
| 93 | + |
| 94 | + @Test |
| 95 | + fun `scan result exposes issue counts score and error state`() { |
| 96 | + val result = ScanResult( |
| 97 | + scanId = "scan-1", |
| 98 | + timestamp = 1L, |
| 99 | + totalNodes = 3, |
| 100 | + issues = listOf( |
| 101 | + issue(A11ySeverity.Error), |
| 102 | + issue(A11ySeverity.Warning), |
| 103 | + issue(A11ySeverity.Warning), |
| 104 | + issue(A11ySeverity.Info), |
| 105 | + ), |
| 106 | + passedRules = 3, |
| 107 | + failedRules = 1, |
| 108 | + ) |
| 109 | + |
| 110 | + assertEquals(1, result.errorCount) |
| 111 | + assertEquals(2, result.warningCount) |
| 112 | + assertEquals(1, result.infoCount) |
| 113 | + assertTrue(result.hasErrors) |
| 114 | + assertEquals(75f, result.overallScore, 0.001f) |
| 115 | + } |
| 116 | + |
| 117 | + @Test |
| 118 | + fun `scan result score is perfect when no rules ran`() { |
| 119 | + val result = ScanResult( |
| 120 | + scanId = "scan-1", |
| 121 | + timestamp = 1L, |
| 122 | + totalNodes = 0, |
| 123 | + issues = emptyList(), |
| 124 | + passedRules = 0, |
| 125 | + failedRules = 0, |
| 126 | + ) |
| 127 | + |
| 128 | + assertFalse(result.hasErrors) |
| 129 | + assertEquals(100f, result.overallScore, 0.001f) |
| 130 | + } |
| 131 | + |
| 132 | + @Test |
| 133 | + fun `severity ordering and rect helpers expose expected values`() { |
| 134 | + assertTrue(A11ySeverity.Error < A11ySeverity.Warning) |
| 135 | + assertTrue(A11ySeverity.Warning < A11ySeverity.Info) |
| 136 | + assertEquals(Rect(0, 0, 0, 0), Rect.Zero) |
| 137 | + assertFalse(Rect(0, 0, 10, 10).isEmpty()) |
| 138 | + assertTrue(Rect(0, 0, 0, 10).isEmpty()) |
| 139 | + assertTrue(Rect(0, 0, 10, 0).isEmpty()) |
| 140 | + } |
| 141 | + |
| 142 | + @Test |
| 143 | + fun `roles list includes all scanner role values`() { |
| 144 | + assertEquals( |
| 145 | + listOf( |
| 146 | + A11yRole.Button, |
| 147 | + A11yRole.Checkbox, |
| 148 | + A11yRole.DropdownList, |
| 149 | + A11yRole.Image, |
| 150 | + A11yRole.RadioButton, |
| 151 | + A11yRole.Switch, |
| 152 | + A11yRole.Tab, |
| 153 | + A11yRole.TextField, |
| 154 | + ), |
| 155 | + A11yRole.entries, |
| 156 | + ) |
| 157 | + } |
| 158 | + |
| 159 | + private fun issue(severity: A11ySeverity) = com.composea11yscanner.core.model.A11yIssue( |
| 160 | + issueId = "issue-${severity.sortOrder}", |
| 161 | + severity = severity, |
| 162 | + ruleId = "rule-${severity.sortOrder}", |
| 163 | + ruleName = "Rule", |
| 164 | + affectedNode = node, |
| 165 | + message = "Message", |
| 166 | + howToFix = "Fix", |
| 167 | + wcagReference = null, |
| 168 | + ) |
| 169 | +} |
0 commit comments