mirror of
https://github.com/nextlevelbuilder/ui-ux-pro-max-skill.git
synced 2026-08-29 08:08:51 +00:00
test(brand): decode subprocess output as UTF-8 and cover the error path
test_sync_brand_to_tokens.py drives sync-brand-to-tokens.cjs through subprocess.run with text=True but no explicit encoding, so Python decodes the pipe with the locale codec. Three of the script's messages carry emoji whose UTF-8 bytes land on cp1252's undefined slots: 0x8F in the "no base hex found" warning (sync-brand-to-tokens.cjs:132), 0x9D in the "brand guidelines not found" error (:198), and 0x8F in the dry-run notice (:223). Decoding then raises inside subprocess's reader thread, the stream comes back as None, and any assertion against it fails with `TypeError: argument of type 'NoneType' is not a container` -- which hides the real result behind an unrelated error. The existing test passes today only because the bundled starter fixture happens to take none of those three paths. Pin the pipe to UTF-8, extracted into a shared _run helper to match the idiom in design-system's test module, and add a regression test for the missing-guidelines path -- the default state of any project that has not run the brand skill yet. That test fails without the encoding fix (stderr is None) and passes with it. Follow-up to #460 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
c87cdc226f
commit
a8f4e7e1ec
@ -24,22 +24,33 @@ TOKENS_STARTER = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_sync_parses_bundled_starter_template(tmp_path):
|
def _run(tmp_path: Path) -> subprocess.CompletedProcess:
|
||||||
node = shutil.which("node")
|
node = shutil.which("node")
|
||||||
if not node:
|
if not node:
|
||||||
pytest.skip("node not available")
|
pytest.skip("node not available")
|
||||||
|
return subprocess.run(
|
||||||
|
[node, str(SCRIPT)],
|
||||||
|
cwd=tmp_path,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
# sync-brand-to-tokens.cjs prints emoji. Without an explicit encoding,
|
||||||
|
# `text=True` decodes the pipe with the locale codec, and several of
|
||||||
|
# those emoji have UTF-8 bytes that cp1252 has no character for
|
||||||
|
# (0x8F in the warning, 0x9D in the error, 0x8F in the dry-run notice).
|
||||||
|
# Decoding then raises inside subprocess's reader thread, the stream
|
||||||
|
# comes back as None, and assertions against it fail with a TypeError
|
||||||
|
# that hides the real result.
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sync_parses_bundled_starter_template(tmp_path):
|
||||||
(tmp_path / "docs").mkdir()
|
(tmp_path / "docs").mkdir()
|
||||||
(tmp_path / "assets").mkdir()
|
(tmp_path / "assets").mkdir()
|
||||||
shutil.copy(BRAND_STARTER, tmp_path / "docs" / "brand-guidelines.md")
|
shutil.copy(BRAND_STARTER, tmp_path / "docs" / "brand-guidelines.md")
|
||||||
shutil.copy(TOKENS_STARTER, tmp_path / "assets" / "design-tokens.json")
|
shutil.copy(TOKENS_STARTER, tmp_path / "assets" / "design-tokens.json")
|
||||||
|
|
||||||
result = subprocess.run(
|
result = _run(tmp_path)
|
||||||
[node, str(SCRIPT)],
|
|
||||||
cwd=tmp_path,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Must not crash (the bug raised an unhandled TypeError).
|
# Must not crash (the bug raised an unhandled TypeError).
|
||||||
assert "TypeError" not in result.stderr, result.stderr
|
assert "TypeError" not in result.stderr, result.stderr
|
||||||
@ -50,3 +61,20 @@ def test_sync_parses_bundled_starter_template(tmp_path):
|
|||||||
assert primitive["primary"]["500"]["$value"] == "#2563EB"
|
assert primitive["primary"]["500"]["$value"] == "#2563EB"
|
||||||
assert primitive["secondary"]["500"]["$value"] == "#8B5CF6"
|
assert primitive["secondary"]["500"]["$value"] == "#8B5CF6"
|
||||||
assert primitive["accent"]["500"]["$value"] == "#10B981"
|
assert primitive["accent"]["500"]["$value"] == "#10B981"
|
||||||
|
|
||||||
|
|
||||||
|
def test_reports_missing_guidelines_without_breaking_the_harness(tmp_path):
|
||||||
|
"""The missing-guidelines path is the one that breaks a locale-decoded pipe.
|
||||||
|
|
||||||
|
It is also the default state of any project that has not run the brand skill
|
||||||
|
yet, so it is the path a contributor hits first. The script prints its error
|
||||||
|
with a leading emoji whose UTF-8 encoding contains 0x9D; cp1252 has no
|
||||||
|
character there, so on Windows this test fails with
|
||||||
|
``TypeError: argument of type 'NoneType' is not a container`` unless the
|
||||||
|
subprocess pipe is pinned to UTF-8.
|
||||||
|
"""
|
||||||
|
result = _run(tmp_path)
|
||||||
|
|
||||||
|
assert result.returncode == 1
|
||||||
|
assert result.stderr is not None
|
||||||
|
assert "Brand guidelines not found" in result.stderr
|
||||||
|
|||||||
@ -24,22 +24,33 @@ TOKENS_STARTER = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_sync_parses_bundled_starter_template(tmp_path):
|
def _run(tmp_path: Path) -> subprocess.CompletedProcess:
|
||||||
node = shutil.which("node")
|
node = shutil.which("node")
|
||||||
if not node:
|
if not node:
|
||||||
pytest.skip("node not available")
|
pytest.skip("node not available")
|
||||||
|
return subprocess.run(
|
||||||
|
[node, str(SCRIPT)],
|
||||||
|
cwd=tmp_path,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
# sync-brand-to-tokens.cjs prints emoji. Without an explicit encoding,
|
||||||
|
# `text=True` decodes the pipe with the locale codec, and several of
|
||||||
|
# those emoji have UTF-8 bytes that cp1252 has no character for
|
||||||
|
# (0x8F in the warning, 0x9D in the error, 0x8F in the dry-run notice).
|
||||||
|
# Decoding then raises inside subprocess's reader thread, the stream
|
||||||
|
# comes back as None, and assertions against it fail with a TypeError
|
||||||
|
# that hides the real result.
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sync_parses_bundled_starter_template(tmp_path):
|
||||||
(tmp_path / "docs").mkdir()
|
(tmp_path / "docs").mkdir()
|
||||||
(tmp_path / "assets").mkdir()
|
(tmp_path / "assets").mkdir()
|
||||||
shutil.copy(BRAND_STARTER, tmp_path / "docs" / "brand-guidelines.md")
|
shutil.copy(BRAND_STARTER, tmp_path / "docs" / "brand-guidelines.md")
|
||||||
shutil.copy(TOKENS_STARTER, tmp_path / "assets" / "design-tokens.json")
|
shutil.copy(TOKENS_STARTER, tmp_path / "assets" / "design-tokens.json")
|
||||||
|
|
||||||
result = subprocess.run(
|
result = _run(tmp_path)
|
||||||
[node, str(SCRIPT)],
|
|
||||||
cwd=tmp_path,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Must not crash (the bug raised an unhandled TypeError).
|
# Must not crash (the bug raised an unhandled TypeError).
|
||||||
assert "TypeError" not in result.stderr, result.stderr
|
assert "TypeError" not in result.stderr, result.stderr
|
||||||
@ -50,3 +61,20 @@ def test_sync_parses_bundled_starter_template(tmp_path):
|
|||||||
assert primitive["primary"]["500"]["$value"] == "#2563EB"
|
assert primitive["primary"]["500"]["$value"] == "#2563EB"
|
||||||
assert primitive["secondary"]["500"]["$value"] == "#8B5CF6"
|
assert primitive["secondary"]["500"]["$value"] == "#8B5CF6"
|
||||||
assert primitive["accent"]["500"]["$value"] == "#10B981"
|
assert primitive["accent"]["500"]["$value"] == "#10B981"
|
||||||
|
|
||||||
|
|
||||||
|
def test_reports_missing_guidelines_without_breaking_the_harness(tmp_path):
|
||||||
|
"""The missing-guidelines path is the one that breaks a locale-decoded pipe.
|
||||||
|
|
||||||
|
It is also the default state of any project that has not run the brand skill
|
||||||
|
yet, so it is the path a contributor hits first. The script prints its error
|
||||||
|
with a leading emoji whose UTF-8 encoding contains 0x9D; cp1252 has no
|
||||||
|
character there, so on Windows this test fails with
|
||||||
|
``TypeError: argument of type 'NoneType' is not a container`` unless the
|
||||||
|
subprocess pipe is pinned to UTF-8.
|
||||||
|
"""
|
||||||
|
result = _run(tmp_path)
|
||||||
|
|
||||||
|
assert result.returncode == 1
|
||||||
|
assert result.stderr is not None
|
||||||
|
assert "Brand guidelines not found" in result.stderr
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user