androidAndroidTest.androidx.constraintlayout.core.XmlBasedTest.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ktx-compose-constraint-layout Show documentation
Show all versions of ktx-compose-constraint-layout Show documentation
Extensions for the Kotlin standard library and third-party libraries.
The newest version!
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.constraintlayout.core
import androidx.constraintlayout.core.widgets.*
import androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour
import org.junit.Assert
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.xml.sax.Attributes
import org.xml.sax.SAXException
import org.xml.sax.helpers.DefaultHandler
import java.io.File
import java.util.*
import javax.xml.parsers.SAXParserFactory
import kotlin.test.Test
/**
* This test the the ConstraintWidget system buy loading XML that contain tags with there positions.
* the xml files can be designed in android studio.
*/
@RunWith(Parameterized::class)
class XmlBasedTest(var file: String) {
var widgetMap: HashMap? = null
var boundsMap: HashMap? = null
var container: ConstraintWidgetContainer? = null
var connectionList: ArrayList? = null
class Connection {
var fromWidget: ConstraintWidget? = null
var fromType: ConstraintAnchor.Type? = null
var toType: ConstraintAnchor.Type? = null
var toName: String? = null
var margin = 0
var gonMargin = Int.MIN_VALUE
}
companion object {
private const val ALLOWED_POSITION_ERROR = 1
private val visibilityMap = HashMap()
private val stringWidthMap: MutableMap = HashMap()
private val stringHeightMap: MutableMap = HashMap()
private val buttonWidthMap: MutableMap = HashMap()
private val buttonHeightMap: MutableMap = HashMap()
private fun rtl(v: String): String {
if (v == "START") return "LEFT"
return if (v == "END") "RIGHT" else v
}
// String dirName = System.getProperty("user.dir") + File.separator+".."+File.separator+".."+File.separator+".."
// +File.separator+"constraintLayout"+File.separator+"core"+File.separator+"src"+File.separator+"test"+File.separator+"resources"+File.separator;
private val dir: String
private get() =// String dirName = System.getProperty("user.dir") + File.separator+".."+File.separator+".."+File.separator+".."
// +File.separator+"constraintLayout"+File.separator+"core"+File.separator+"src"+File.separator+"test"+File.separator+"resources"+File.separator;
System.getProperty("user.dir") + "/src/test/resources/"
@Parameterized.Parameters
fun genListOfName(): Array> {
val dirName: String = XmlBasedTest.Companion.dir
Assert.assertTrue(File(dirName).exists())
val f = File(dirName).listFiles { pathname: File -> pathname.name.startsWith("check") }
Assert.assertNotNull(f)
Arrays.sort(f) { o1: File, o2: File -> o1.name.compareTo(o2.name) }
val ret = Array(f.size) { arrayOfNulls(1) }
for (i in ret.indices) {
ret[i][0] = f[i].absolutePath
}
return ret
}
/**
* Calculate the Factorial of n
*
* @param N input number
* @return Factorial of n
*/
fun fact(N: Int): Int {
var N = N
var ret = 1
while (N > 0) {
ret *= N--
}
return ret
}
/**
* Simple dimension parser
* Multiply dp units by 3 because we simulate a screen with 3 pixels per dp
*
* @param dim
* @return
*/
fun parseDim(dim: String): Int {
if (dim.endsWith("dp")) {
return 3 * dim.substring(0, dim.length - 2).toInt()
}
return if (dim == "wrap_content") {
-1
} else -2
}
private fun rightPad(s: String, n: Int): String {
var s = s
s = s + String(ByteArray(n)).replace('\u0000', ' ')
return s.substring(0, n)
}
private fun R(s: String): String {
var s = s
s = " $s"
return s.substring(s.length - 13)
}
/**
* Ordered array (1,2,3...) will be cycled till the order is reversed (9,8,7...)
*
* @param array to be carried
* @return false when the order is reversed
*/
private fun nextPermutation(array: IntArray): Boolean {
var i = array.size - 1
while (i > 0 && array[i - 1] >= array[i]) {
i--
}
if (i <= 0) return false
var j = array.size - 1
while (array[j] <= array[i - 1]) {
j--
}
var temp = array[i - 1]
array[i - 1] = array[j]
array[j] = temp
j = array.size - 1
while (i < j) {
temp = array[i]
array[i] = array[j]
array[j] = temp
i++
j--
}
return true
}
init {
XmlBasedTest.Companion.visibilityMap.put("gone", ConstraintWidget.GONE)
XmlBasedTest.Companion.visibilityMap.put("visible", ConstraintWidget.VISIBLE)
XmlBasedTest.Companion.visibilityMap.put("invisible", ConstraintWidget.INVISIBLE)
XmlBasedTest.Companion.stringWidthMap.put("TextView", 171)
XmlBasedTest.Companion.stringWidthMap.put("Button", 107)
XmlBasedTest.Companion.stringWidthMap.put("Hello World!", 200)
XmlBasedTest.Companion.stringHeightMap.put("TextView", 57)
XmlBasedTest.Companion.stringHeightMap.put("Button", 51)
XmlBasedTest.Companion.stringHeightMap.put("Hello World!", 51)
val s = "12345678 12345678 12345678 12345678 12345678 12345678 12345678 12345678 12345678 12345678 12345678 12345678 12345678 12345678"
XmlBasedTest.Companion.stringWidthMap.put(s, 984)
XmlBasedTest.Companion.stringHeightMap.put(s, 204)
XmlBasedTest.Companion.buttonWidthMap.put("Button", 264)
XmlBasedTest.Companion.buttonHeightMap.put("Button", 144)
}
}
@Test
fun testAccessToResources() {
val dirName: String = XmlBasedTest.Companion.dir
Assert.assertTrue(" could not find dir $dirName", File(dirName).exists())
val names: Array> = XmlBasedTest.Companion.genListOfName()
Assert.assertTrue(" Could not get Path $dirName", names.size > 1)
}
fun dim(w: ConstraintWidget?): String {
if (w is Guideline) {
return w.left.toString() + "," + w.top + "," + 0 + "," + 0
}
return if (w!!.visibility == ConstraintWidget.GONE) {
0.toString() + "," + 0 + "," + 0 + "," + 0
} else w.left.toString() + "," + w.top + "," + w.width + "," + w.height
}
@Test
fun testSolverXML() {
parseXML(file)
container!!.optimizationLevel = Optimizer.OPTIMIZATION_NONE
val perm = IntArray(boundsMap!!.size)
for (i in perm.indices) {
perm[i] = i
}
val total: Int = XmlBasedTest.Companion.fact(perm.size)
val skip = 1 + total / 1000
populateContainer(perm)
makeConnections()
layout()
validate()
var k = 0
while (XmlBasedTest.Companion.nextPermutation(perm)) {
k++
if (k % skip != 0) continue
populateContainer(perm)
makeConnections()
layout()
validate()
}
}
@Test
fun testDirectResolutionXML() {
parseXML(file)
container!!.optimizationLevel = Optimizer.OPTIMIZATION_STANDARD
val perm = IntArray(boundsMap!!.size)
for (i in perm.indices) {
perm[i] = i
}
val total: Int = XmlBasedTest.Companion.fact(perm.size)
val skip = 1 + total / 1000
populateContainer(perm)
makeConnections()
layout()
validate()
var k = 0
while (XmlBasedTest.Companion.nextPermutation(perm)) {
k++
if (k % skip != 0) continue
populateContainer(perm)
makeConnections()
layout()
validate()
}
}
/**
* Compare two string containing comer separated integers
*
* @param a
* @param b
* @return
*/
private fun isSame(a: String?, b: String?): Boolean {
if (a == null || b == null) {
return false
}
val a_split = a.split(",".toRegex()).toTypedArray()
val b_split = b.split(",".toRegex()).toTypedArray()
if (a_split.size != b_split.size) {
return false
}
for (i in a_split.indices) {
if (a_split[i].length == 0) {
return false
}
var error: Int = XmlBasedTest.Companion.ALLOWED_POSITION_ERROR
if (b_split[i].startsWith("+")) {
error += 10
}
val a_value = a_split[i].toInt()
val b_value = b_split[i].toInt()
if (Math.abs(a_value - b_value) > error) {
return false
}
}
return true
}
/**
* parse the XML file
*
* @param fileName
*/
private fun parseXML(fileName: String) {
println(fileName)
container = ConstraintWidgetContainer(0, 0, 1080, 1920)
container!!.debugName = "parent"
widgetMap = HashMap()
boundsMap = HashMap()
connectionList = ArrayList()
val handler: DefaultHandler = object : DefaultHandler() {
var parentId: String? = null
@Throws(SAXException::class)
override fun startDocument() {
}
@Throws(SAXException::class)
override fun endDocument() {
}
@Throws(SAXException::class)
override fun startElement(
namespaceURI: String,
localName: String,
qName: String,
attributes: Attributes
) {
if (qName != null) {
val androidAttrs: MutableMap = HashMap()
val appAttrs: MutableMap = HashMap()
val widgetConstraints: MutableMap = HashMap()
val widgetGoneMargins: MutableMap = HashMap()
val widgetMargins: MutableMap = HashMap()
for (i in 0 until attributes.length) {
val attrName = attributes.getLocalName(i)
val attrValue = attributes.getValue(i)
if (!attrName.contains(":")) {
continue
}
if (attrValue.trim { it <= ' ' }.isEmpty()) {
continue
}
val parts = attrName.split(":".toRegex()).toTypedArray()
val scheme = parts[0]
val attr = parts[1]
if (scheme == "android") {
androidAttrs[attr] = attrValue
if (attr.startsWith("layout_margin")) {
widgetMargins[attr] = attrValue
}
} else if (scheme == "app") {
appAttrs[attr] = attrValue
if (attr == "layout_constraintDimensionRatio") {
} else if (attr == "layout_constraintGuide_begin") {
} else if (attr == "layout_constraintGuide_percent") {
} else if (attr == "layout_constraintGuide_end") {
} else if (attr == "layout_constraintHorizontal_bias") {
} else if (attr == "layout_constraintVertical_bias") {
} else if (attr.startsWith("layout_constraint")) {
widgetConstraints[attr] = attrValue
}
if (attr.startsWith("layout_goneMargin")) {
widgetGoneMargins[attr] = attrValue
}
}
}
val id = androidAttrs["id"]
val tag = androidAttrs["tag"]
val layoutWidth: Int = XmlBasedTest.Companion.parseDim(androidAttrs["layout_width"] ?: "")
val layoutHeight: Int = XmlBasedTest.Companion.parseDim(androidAttrs["layout_height"] ?: "")
val text = androidAttrs["text"]
val visibility = androidAttrs["visibility"]
val orientation = androidAttrs["orientation"]
if (qName.endsWith("ConstraintLayout")) {
if (id != null) {
container!!.debugName = id
}
widgetMap!![container!!.debugName] = container!!
widgetMap!!["parent"] = container!!
} else if (qName.endsWith("Guideline")) {
val guideline = Guideline()
if (id != null) {
guideline.debugName = id
}
widgetMap!![guideline.debugName] = guideline
boundsMap!![guideline] = tag
val horizontal = "horizontal" == orientation
println("Guideline " + id + " " + if (horizontal) "HORIZONTAL" else "VERTICAL")
guideline.orientation = if (horizontal) Guideline.HORIZONTAL else Guideline.VERTICAL
val constraintGuideBegin = appAttrs["layout_constraintGuide_begin"]
val constraintGuidePercent = appAttrs["layout_constraintGuide_percent"]
val constraintGuideEnd = appAttrs["layout_constraintGuide_end"]
if (constraintGuideBegin != null) {
guideline.setGuideBegin(XmlBasedTest.Companion.parseDim(constraintGuideBegin))
println("Guideline " + id + " setGuideBegin " + XmlBasedTest.Companion.parseDim(constraintGuideBegin))
} else if (constraintGuidePercent != null) {
guideline.setGuidePercent(constraintGuidePercent.toFloat())
println("Guideline " + id + " setGuidePercent " + constraintGuidePercent.toFloat())
} else if (constraintGuideEnd != null) {
guideline.setGuideEnd(XmlBasedTest.Companion.parseDim(constraintGuideEnd))
println("Guideline " + id + " setGuideBegin " + XmlBasedTest.Companion.parseDim(constraintGuideEnd))
}
println(">>>>>>>>>>>> $guideline")
} else {
val widget = ConstraintWidget(200, 51)
widget.baselineDistance = 28
val connect = arrayOfNulls(5)
val widgetLayoutConstraintDimensionRatio = appAttrs["layout_constraintDimensionRatio"]
val widgetLayoutConstraintHorizontalBias = appAttrs["layout_constraintHorizontal_bias"]
val widgetLayoutConstraintVerticalBias = appAttrs["layout_constraintVertical_bias"]
if (id != null) {
widget.debugName = id
} else {
widget.debugName = "widget" + (widgetMap!!.size + 1)
}
if (tag != null) {
boundsMap!![widget] = tag
}
var hBehaviour = DimensionBehaviour.FIXED
if (layoutWidth == 0) {
hBehaviour = DimensionBehaviour.MATCH_CONSTRAINT
widget.setDimension(layoutWidth, widget.height)
} else if (layoutWidth == -1) {
hBehaviour = DimensionBehaviour.WRAP_CONTENT
} else {
widget.setDimension(layoutWidth, widget.height)
}
widget.horizontalDimensionBehaviour = hBehaviour
var vBehaviour = DimensionBehaviour.FIXED
if (layoutHeight == 0) {
vBehaviour = DimensionBehaviour.MATCH_CONSTRAINT
widget.setDimension(widget.width, layoutHeight)
} else if (layoutHeight == -1) {
vBehaviour = DimensionBehaviour.WRAP_CONTENT
} else {
widget.setDimension(widget.width, layoutHeight)
}
widget.verticalDimensionBehaviour = vBehaviour
if (text != null) {
print("text = \"$text\"")
val wmap: Map = if (qName == "Button") XmlBasedTest.Companion.buttonWidthMap else XmlBasedTest.Companion.stringWidthMap
val hmap: Map = if (qName == "Button") XmlBasedTest.Companion.buttonHeightMap else XmlBasedTest.Companion.stringHeightMap
if (wmap.containsKey(text) && widget.horizontalDimensionBehaviour === DimensionBehaviour.WRAP_CONTENT) {
widget.width = wmap[text]!!
}
if (hmap.containsKey(text) && widget.verticalDimensionBehaviour === DimensionBehaviour.WRAP_CONTENT) {
widget.height = hmap[text]!!
}
}
if (visibility != null) {
widget.visibility = XmlBasedTest.Companion.visibilityMap.get(visibility) ?: 0
}
if (widgetLayoutConstraintDimensionRatio != null) {
widget.setDimensionRatio(widgetLayoutConstraintDimensionRatio)
}
if (widgetLayoutConstraintHorizontalBias != null) {
println("widgetLayoutConstraintHorizontalBias $widgetLayoutConstraintHorizontalBias")
widget.horizontalBiasPercent = widgetLayoutConstraintHorizontalBias.toFloat()
}
if (widgetLayoutConstraintVerticalBias != null) {
println("widgetLayoutConstraintVerticalBias $widgetLayoutConstraintVerticalBias")
widget.verticalBiasPercent = widgetLayoutConstraintVerticalBias.toFloat()
}
val constraintKeySet: Set = widgetConstraints.keys
val constraintKeys = constraintKeySet.toTypedArray()
for (i in constraintKeys.indices) {
val attrName = constraintKeys[i]
val attrValue = widgetConstraints[attrName]
val sp = attrName.substring("layout_constraint".length).split("_to".toRegex()).toTypedArray()
val fromString: String = XmlBasedTest.Companion.rtl(sp[0].toUpperCase())
val from = ConstraintAnchor.Type.valueOf(fromString)
val toString: String = XmlBasedTest.Companion.rtl(sp[1].substring(0, sp[1].length - 2).toUpperCase())
val to = ConstraintAnchor.Type.valueOf(toString)
val side = from.ordinal - 1
if (connect[side] == null) {
connect[side] = XmlBasedTest.Connection()
}
connect[side]!!.fromWidget = widget
connect[side]!!.fromType = from
connect[side]!!.toType = to
connect[side]!!.toName = attrValue
}
val goneMarginSet: Set = widgetGoneMargins.keys
val goneMargins = goneMarginSet.toTypedArray()
for (i in goneMargins.indices) {
val attrName = goneMargins[i]
val attrValue = widgetGoneMargins[attrName]
val marginSide: String = XmlBasedTest.Companion.rtl(attrName.substring("layout_goneMargin".length).toUpperCase())
val marginType = ConstraintAnchor.Type.valueOf(marginSide)
val side = marginType.ordinal - 1
if (connect[side] == null) {
connect[side] = XmlBasedTest.Connection()
}
connect[side]!!.gonMargin = 3 * attrValue!!.substring(0, attrValue.length - 2).toInt()
}
val marginSet: Set = widgetMargins.keys
val margins = marginSet.toTypedArray()
for (i in margins.indices) {
val attrName = margins[i]
val attrValue = widgetMargins[attrName]
// System.out.println("margin [" + attrName + "] by [" + attrValue +"]");
val marginSide: String = XmlBasedTest.Companion.rtl(attrName.substring("layout_margin".length).toUpperCase())
val marginType = ConstraintAnchor.Type.valueOf(marginSide)
val side = marginType.ordinal - 1
if (connect[side] == null) {
connect[side] = XmlBasedTest.Connection()
}
connect[side]!!.margin = 3 * attrValue!!.substring(0, attrValue.length - 2).toInt()
}
widgetMap!![widget.debugName] = widget
for (i in connect.indices) {
if (connect[i] != null) {
connectionList!!.add(connect[i])
}
}
}
}
}
}
val file = File(fileName)
val spf = SAXParserFactory.newInstance()
try {
val saxParser = spf.newSAXParser()
val xmlReader = saxParser.xmlReader
xmlReader.contentHandler = handler
xmlReader.parse(file.toURI().toString())
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun populateContainer(order: IntArray) {
println(Arrays.toString(order))
val widgetSet: Array = boundsMap!!.keys.toTypedArray()
for (i in widgetSet.indices) {
val widget = widgetSet[order[i]]
if (widget!!.debugName == "parent") {
continue
}
val hBehaviour = widget.horizontalDimensionBehaviour
val vBehaviour = widget.verticalDimensionBehaviour
if (widget is Guideline) {
val copy = Guideline()
copy.copy(widget, HashMap())
container!!.remove(widget)
widget.copy(copy, HashMap())
} else {
val copy = ConstraintWidget()
copy.copy(widget, HashMap())
container!!.remove(widget)
widget.copy(copy, HashMap())
}
widget.horizontalDimensionBehaviour = hBehaviour
widget.verticalDimensionBehaviour = vBehaviour
container!!.add(widget)
}
}
private fun makeConnections() {
for (connection in connectionList!!) {
var toConnect: ConstraintWidget?
toConnect = if (connection!!.toName.equals("parent", ignoreCase = true) || connection.toName == container!!.debugName) {
container
} else {
widgetMap!![connection.toName]
}
if (toConnect == null) {
println(" " + connection.toName)
} else {
connection.fromWidget!!.connect(connection.fromType!!, toConnect, connection.toType!!, connection.margin)
connection.fromWidget!!.setGoneMargin(connection.fromType, connection.gonMargin)
}
}
}
private fun layout() {
container!!.layout()
}
private fun validate() {
val root = widgetMap!!.remove("parent") as ConstraintWidgetContainer?
val keys = widgetMap!!.keys.toTypedArray()
var ok = true
val layout = StringBuilder("\n")
for (key in keys) {
if (key!!.contains("activity_main")) {
continue
}
val widget = widgetMap!![key]
val bounds = boundsMap!![widget]
val dim = dim(widget)
val same = isSame(dim, bounds)
val compare: String = XmlBasedTest.Companion.rightPad(key, 17) + XmlBasedTest.Companion.rightPad(dim, 15) + " " + bounds
ok = ok and same
layout.append(compare).append("\n")
}
Assert.assertTrue(layout.toString(), ok)
}
@Test
fun SimpleTest() {
val root = ConstraintWidgetContainer(0, 0, 1080, 1920)
val A = ConstraintWidget(0, 0, 200, 51)
A.horizontalDimensionBehaviour = DimensionBehaviour.WRAP_CONTENT
A.verticalDimensionBehaviour = DimensionBehaviour.WRAP_CONTENT
A.debugName = "A"
A.connect(ConstraintAnchor.Type.LEFT, root, ConstraintAnchor.Type.LEFT, 0)
A.connect(ConstraintAnchor.Type.TOP, root, ConstraintAnchor.Type.TOP, 0)
A.connect(ConstraintAnchor.Type.RIGHT, root, ConstraintAnchor.Type.RIGHT, 0)
A.connect(ConstraintAnchor.Type.BOTTOM, root, ConstraintAnchor.Type.BOTTOM, 0)
root.add(A)
root.layout()
println("f) A: " + A + " " + A.width + "," + A.height)
}
@Test
fun GuideLineTest() {
val root = ConstraintWidgetContainer(0, 0, 1080, 1920)
val A = ConstraintWidget(0, 0, 200, 51)
val guideline = Guideline()
root.add(guideline)
guideline.setGuidePercent(0.50f)
guideline.orientation = Guideline.VERTICAL
guideline.debugName = "guideline"
A.horizontalDimensionBehaviour = DimensionBehaviour.WRAP_CONTENT
A.verticalDimensionBehaviour = DimensionBehaviour.WRAP_CONTENT
A.debugName = "A"
A.connect(ConstraintAnchor.Type.LEFT, guideline, ConstraintAnchor.Type.LEFT, 0)
A.connect(ConstraintAnchor.Type.TOP, root, ConstraintAnchor.Type.TOP, 0)
A.connect(ConstraintAnchor.Type.RIGHT, root, ConstraintAnchor.Type.RIGHT, 0)
A.connect(ConstraintAnchor.Type.BOTTOM, root, ConstraintAnchor.Type.BOTTOM, 0)
root.add(A)
root.layout()
println("f) A: " + A + " " + A.width + "," + A.height)
println("f) A: " + guideline + " " + guideline.width + "," + guideline.height)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy