Skip to content

Commit 6c92445

Browse files
author
Onimock
committed
Fix: accept _ for custom properties
1 parent 295c205 commit 6c92445

File tree

2 files changed

+166
-1
lines changed

2 files changed

+166
-1
lines changed

src/qss_parser/qss_parser.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -778,7 +778,10 @@ def _is_valid_property_name(self, name: str) -> bool:
778778
Returns:
779779
bool: True if the property name is valid, False otherwise.
780780
"""
781-
return bool(re.match(r"^[a-zA-Z][a-zA-Z0-9-]*$", name))
781+
if name.startswith("qproperty-"):
782+
return bool(re.match(r"^qproperty-[a-zA-Z_][a-zA-Z0-9_-]*$", name))
783+
else:
784+
return bool(re.match(r"^[a-zA-Z][a-zA-Z0-9-]*$", name))
782785

783786

784787
@dataclass

tests/test_qss_parser.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,5 +1313,167 @@ def test_to_string_complex_nested_selectors_with_attribute_selector_space_invali
13131313
)
13141314

13151315

1316+
class TestQSSParserQProperty(unittest.TestCase):
1317+
def setUp(self) -> None:
1318+
"""
1319+
Set up the test environment for qproperty parsing tests.
1320+
"""
1321+
self.parser: QSSParser = QSSParser()
1322+
self.errors: List[str] = []
1323+
self.parser.on(ParserEvent.ERROR_FOUND, lambda error: self.errors.append(error))
1324+
self.qss: str = """
1325+
QPushButton {
1326+
qproperty-icon: url(:/icons/test.png);
1327+
qproperty-text: "Click Me";
1328+
color: blue;
1329+
}
1330+
#customButton {
1331+
qproperty-enabled: false;
1332+
background: gray;
1333+
}
1334+
.iconButton[qproperty-icon="url(:/icons/test.png)"] {
1335+
border: 1px solid black;
1336+
}
1337+
"""
1338+
1339+
def test_parse_qproperty_attributes(self) -> None:
1340+
"""
1341+
Test parsing QSS with valid qproperty attributes.
1342+
"""
1343+
self.parser.parse(self.qss)
1344+
self.assertEqual(
1345+
len(self.parser._state.rules), 3, "Should parse all rules correctly"
1346+
)
1347+
self.assertEqual(
1348+
self.errors, [], "Valid qproperty QSS should produce no errors"
1349+
)
1350+
1351+
# Test QPushButton rule
1352+
push_button_rule: QSSRule = self.parser._state.rules[0]
1353+
self.assertEqual(push_button_rule.selector, "QPushButton")
1354+
self.assertEqual(len(push_button_rule.properties), 3)
1355+
self.assertEqual(push_button_rule.properties[0].name, "qproperty-icon")
1356+
self.assertEqual(push_button_rule.properties[0].value, "url(:/icons/test.png)")
1357+
self.assertEqual(push_button_rule.properties[1].name, "qproperty-text")
1358+
self.assertEqual(push_button_rule.properties[1].value, '"Click Me"')
1359+
self.assertEqual(push_button_rule.properties[2].name, "color")
1360+
self.assertEqual(push_button_rule.properties[2].value, "blue")
1361+
1362+
# Test #customButton rule
1363+
custom_button_rule: QSSRule = self.parser._state.rules[1]
1364+
self.assertEqual(custom_button_rule.selector, "#customButton")
1365+
self.assertEqual(len(custom_button_rule.properties), 2)
1366+
self.assertEqual(custom_button_rule.properties[0].name, "qproperty-enabled")
1367+
self.assertEqual(custom_button_rule.properties[0].value, "false")
1368+
self.assertEqual(custom_button_rule.properties[1].name, "background")
1369+
self.assertEqual(custom_button_rule.properties[1].value, "gray")
1370+
1371+
# Test .iconButton rule with qproperty in selector
1372+
icon_button_rule: QSSRule = self.parser._state.rules[2]
1373+
self.assertEqual(
1374+
icon_button_rule.selector,
1375+
'.iconButton[qproperty-icon="url(:/icons/test.png)"]',
1376+
)
1377+
self.assertEqual(len(icon_button_rule.properties), 1)
1378+
self.assertEqual(icon_button_rule.properties[0].name, "border")
1379+
self.assertEqual(icon_button_rule.properties[0].value, "1px solid black")
1380+
self.assertEqual(
1381+
icon_button_rule.attributes, ['[qproperty-icon="url(:/icons/test.png)"]']
1382+
)
1383+
1384+
def test_get_styles_for_qproperty(self) -> None:
1385+
"""
1386+
Test style retrieval for a widget with qproperty attributes.
1387+
"""
1388+
self.parser.parse(self.qss)
1389+
widget: Mock = Mock()
1390+
widget.objectName.return_value = "customButton"
1391+
widget.metaObject.return_value.className.return_value = "QPushButton"
1392+
1393+
stylesheet: str = self.parser.get_styles_for(widget)
1394+
expected: str = """#customButton {
1395+
qproperty-enabled: false;
1396+
background: gray;
1397+
}"""
1398+
self.assertEqual(stylesheet.strip(), expected.strip())
1399+
self.assertEqual(
1400+
self.errors, [], "Valid qproperty style retrieval should produce no errors"
1401+
)
1402+
1403+
def test_get_styles_for_qproperty_with_class_and_object_name(self) -> None:
1404+
"""
1405+
Test style retrieval including class styles for a widget with qproperty attributes.
1406+
"""
1407+
self.parser.parse(self.qss)
1408+
widget: Mock = Mock()
1409+
widget.objectName.return_value = "customButton"
1410+
widget.metaObject.return_value.className.return_value = "QPushButton"
1411+
1412+
stylesheet: str = self.parser.get_styles_for(
1413+
widget, include_class_if_object_name=True
1414+
)
1415+
expected: str = """#customButton {
1416+
qproperty-enabled: false;
1417+
background: gray;
1418+
}
1419+
QPushButton {
1420+
qproperty-icon: url(:/icons/test.png);
1421+
qproperty-text: "Click Me";
1422+
color: blue;
1423+
}"""
1424+
self.assertEqual(stylesheet.strip(), expected.strip())
1425+
self.assertEqual(
1426+
self.errors, [], "Valid qproperty style retrieval should produce no errors"
1427+
)
1428+
1429+
def test_parse_invalid_qproperty_name(self) -> None:
1430+
"""
1431+
Test parsing QSS with an invalid qproperty name.
1432+
"""
1433+
qss: str = """
1434+
QPushButton {
1435+
qproperty-123invalid: true;
1436+
color: blue;
1437+
}
1438+
"""
1439+
self.parser.parse(qss)
1440+
self.assertEqual(
1441+
len(self.parser._state.rules), 1, "Should parse rule with valid properties"
1442+
)
1443+
rule: QSSRule = self.parser._state.rules[0]
1444+
self.assertEqual(len(rule.properties), 1, "Should skip invalid qproperty")
1445+
self.assertEqual(rule.properties[0].name, "color")
1446+
self.assertEqual(rule.properties[0].value, "blue")
1447+
self.assertEqual(len(self.errors), 1, "Should report invalid qproperty name")
1448+
self.assertTrue(
1449+
"Invalid property name: 'qproperty-123invalid'" in self.errors[0]
1450+
)
1451+
1452+
def test_to_string_with_qproperty(self) -> None:
1453+
"""
1454+
Test to_string() output for QSS rules with qproperty attributes.
1455+
"""
1456+
self.parser.parse(self.qss)
1457+
expected: str = """QPushButton {
1458+
qproperty-icon: url(:/icons/test.png);
1459+
qproperty-text: "Click Me";
1460+
color: blue;
1461+
}
1462+
1463+
#customButton {
1464+
qproperty-enabled: false;
1465+
background: gray;
1466+
}
1467+
1468+
.iconButton[qproperty-icon="url(:/icons/test.png)"] {
1469+
border: 1px solid black;
1470+
}
1471+
"""
1472+
self.assertEqual(self.parser.to_string(), expected)
1473+
self.assertEqual(
1474+
self.errors, [], "Valid qproperty QSS should produce no errors"
1475+
)
1476+
1477+
13161478
if __name__ == "__main__":
13171479
unittest.main()

0 commit comments

Comments
 (0)