diff --git a/.github/workflows/sync-tag-definitions.yml b/.github/workflows/sync-tag-definitions.yml
index d48fef3..c8ff2d2 100644
--- a/.github/workflows/sync-tag-definitions.yml
+++ b/.github/workflows/sync-tag-definitions.yml
@@ -29,167 +29,11 @@ jobs:
- name: Fetch tag type definitions from OpenEPaperLink
id: fetch
- run: |
- python3 << 'PYEOF'
- import urllib.request
- import json
- import re
-
- print("Fetching tag type files from OpenEPaperLink repository...")
-
- # Fetch the directory listing
- url = "https://github.com/OpenEPaperLink/OpenEPaperLink/tree/master/resources/tagtypes"
- headers = {'User-Agent': 'Mozilla/5.0'}
- req = urllib.request.Request(url, headers=headers)
-
- try:
- with urllib.request.urlopen(req, timeout=30) as response:
- html = response.read().decode('utf-8')
- json_files = re.findall(r'([0-9a-fA-F]+\.json)', html)
- json_files = sorted(set(json_files))
- print(f"Found {len(json_files)} tag type files")
- except Exception as e:
- print(f"Error fetching file list: {e}")
- exit(1)
-
- # Fetch all tag type definitions
- tag_types = {}
- errors = []
-
- for filename in json_files:
- url = f"https://raw.githubusercontent.com/OpenEPaperLink/OpenEPaperLink/master/resources/tagtypes/{filename}"
- try:
- with urllib.request.urlopen(url, timeout=10) as response:
- data = json.loads(response.read().decode('utf-8'))
- type_id = int(filename.replace('.json', ''), 16)
-
- # Extract only required fields
- tag_types[type_id] = {
- 'version': data.get('version'),
- 'name': data.get('name'),
- 'width': data.get('width'),
- 'height': data.get('height'),
- }
- except Exception as e:
- errors.append(f"Error fetching {filename}: {e}")
-
- if errors:
- for error in errors:
- print(error)
-
- print(f"Successfully fetched {len(tag_types)} tag type definitions")
-
- # Save to file for next step
- with open('new_tag_types.json', 'w') as f:
- json.dump(tag_types, f, indent=2)
-
- print("Tag types saved to new_tag_types.json")
- PYEOF
+ run: python3 scripts/fetch_tag_types.py new_tag_types.json
- name: Generate updated tag_types.py
id: generate
- run: |
- python3 << 'PYEOF'
- import json
- import re
-
- # Load new tag types
- with open('new_tag_types.json', 'r') as f:
- new_tag_types = json.load(f)
-
- # Read current tag_types.py
- with open('custom_components/opendisplay/tag_types.py', 'r') as f:
- content = f.read()
-
- # Extract current fallback definitions
- match = re.search(r'fallback_definitions = \{(.*?)\n \}', content, re.DOTALL)
- if not match:
- print("Error: Could not find fallback_definitions in tag_types.py")
- exit(1)
-
- current_definitions = match.group(1)
-
- # Parse current definitions to dict
- current_types = {}
- for line in current_definitions.split('\n'):
- match = re.match(r'\s+(\d+):', line)
- if match:
- type_id = int(match.group(1))
- current_types[type_id] = line.strip()
-
- print(f"Current definitions: {len(current_types)} types")
- print(f"New definitions: {len(new_tag_types)} types")
-
- # Check if there are differences
- changed = False
- added = []
- removed = []
- modified = []
-
- # Find added and modified
- for type_id in sorted(new_tag_types.keys()):
- if type_id not in current_types:
- added.append(type_id)
- changed = True
- else:
- # Compare values
- new_line = f'{type_id}: {json.dumps(new_tag_types[str(type_id)])}'
- if new_line not in current_types[type_id]:
- modified.append(type_id)
- changed = True
-
- # Find removed
- for type_id in current_types:
- if str(type_id) not in new_tag_types and type_id not in [int(k) for k in new_tag_types.keys()]:
- removed.append(type_id)
- changed = True
-
- # Generate new fallback_definitions content
- lines = []
- for type_id in sorted([int(k) for k in new_tag_types.keys()]):
- type_data = new_tag_types[str(type_id)]
- line = f' {type_id}: {json.dumps(type_data)},'
- lines.append(line)
-
- new_fallback = '\n'.join(lines)
-
- # Replace in content
- new_content = re.sub(
- r'(fallback_definitions = \{)\n.*?\n( \})',
- r'\1\n' + new_fallback + '\n\2',
- content,
- flags=re.DOTALL
- )
-
- # Write updated file
- with open('custom_components/opendisplay/tag_types.py', 'w') as f:
- f.write(new_content)
-
- # Create summary
- summary = []
- if added:
- summary.append(f"Added: {len(added)} types ({', '.join(map(str, added[:5]))}{'...' if len(added) > 5 else ''})")
- if removed:
- summary.append(f"Removed: {len(removed)} types ({', '.join(map(str, removed[:5]))}{'...' if len(removed) > 5 else ''})")
- if modified:
- summary.append(f"Modified: {len(modified)} types ({', '.join(map(str, modified[:5]))}{'...' if len(modified) > 5 else ''})")
-
- if changed:
- print("CHANGED=true")
- print(f"SUMMARY={'|'.join(summary)}")
- with open('CHANGES_SUMMARY.txt', 'w') as f:
- f.write('\n'.join(summary))
- else:
- print("CHANGED=false")
- print("No changes detected")
-
- # Set output for GitHub Actions
- import os
- with open(os.environ['GITHUB_OUTPUT'], 'a') as f:
- f.write(f"changed={'true' if changed else 'false'}\n")
- if summary:
- f.write(f"summary={'|'.join(summary)}\n")
- PYEOF
+ run: python3 scripts/generate_tag_types.py new_tag_types.json
- name: Create Pull Request
if: steps.generate.outputs.changed == 'true'
diff --git a/scripts/fetch_tag_types.py b/scripts/fetch_tag_types.py
new file mode 100644
index 0000000..b891cc8
--- /dev/null
+++ b/scripts/fetch_tag_types.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python3
+"""Fetch tag type definitions from the OpenEPaperLink repository.
+
+Downloads all tag type JSON files from the OpenEPaperLink GitHub repository
+and saves them as a consolidated JSON file for further processing.
+"""
+
+import json
+import re
+import sys
+import urllib.request
+
+
+GITHUB_TREE_URL = (
+ "https://github.com/OpenEPaperLink/OpenEPaperLink/tree/master/resources/tagtypes"
+)
+GITHUB_RAW_URL = (
+ "https://raw.githubusercontent.com/OpenEPaperLink/OpenEPaperLink"
+ "/master/resources/tagtypes"
+)
+
+
+def fetch_file_list():
+ """Fetch the list of tag type JSON files from the repository."""
+ print("Fetching tag type files from OpenEPaperLink repository...")
+ headers = {"User-Agent": "Mozilla/5.0"}
+ req = urllib.request.Request(GITHUB_TREE_URL, headers=headers)
+
+ with urllib.request.urlopen(req, timeout=30) as response:
+ html = response.read().decode("utf-8")
+ json_files = re.findall(r"([0-9a-fA-F]+\.json)", html)
+ json_files = sorted(set(json_files))
+ print(f"Found {len(json_files)} tag type files")
+ return json_files
+
+
+def fetch_tag_types(json_files):
+ """Fetch and parse all tag type definitions."""
+ tag_types = {}
+ errors = []
+
+ for filename in json_files:
+ url = f"{GITHUB_RAW_URL}/{filename}"
+ try:
+ with urllib.request.urlopen(url, timeout=10) as response:
+ data = json.loads(response.read().decode("utf-8"))
+ type_id = int(filename.replace(".json", ""), 16)
+
+ tag_types[type_id] = {
+ "version": data.get("version"),
+ "name": data.get("name"),
+ "width": data.get("width"),
+ "height": data.get("height"),
+ }
+ except Exception as e:
+ errors.append(f"Error fetching {filename}: {e}")
+
+ if errors:
+ for error in errors:
+ print(error)
+
+ print(f"Successfully fetched {len(tag_types)} tag type definitions")
+ return tag_types
+
+
+def main():
+ """Fetch tag type definitions and save to a JSON file."""
+ output_file = sys.argv[1] if len(sys.argv) > 1 else "new_tag_types.json"
+
+ try:
+ json_files = fetch_file_list()
+ except Exception as e:
+ print(f"Error fetching file list: {e}")
+ sys.exit(1)
+
+ tag_types = fetch_tag_types(json_files)
+
+ with open(output_file, "w") as f:
+ json.dump(tag_types, f, indent=2)
+
+ print(f"Tag types saved to {output_file}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/generate_tag_types.py b/scripts/generate_tag_types.py
new file mode 100644
index 0000000..a467bb3
--- /dev/null
+++ b/scripts/generate_tag_types.py
@@ -0,0 +1,157 @@
+#!/usr/bin/env python3
+"""Generate updated tag_types.py from fetched tag type definitions.
+
+Reads a JSON file of tag type definitions (produced by fetch_tag_types.py),
+compares them against the current fallback definitions in tag_types.py,
+and updates the file if there are changes.
+
+Sets GitHub Actions outputs for downstream workflow steps.
+"""
+
+import json
+import os
+import re
+import sys
+
+
+TAG_TYPES_PATH = "custom_components/opendisplay/tag_types.py"
+FALLBACK_PATTERN = re.compile(
+ r"( fallback_definitions = \{)\n(.*?)\n( \})", re.DOTALL
+)
+ENTRY_PATTERN = re.compile(r"\s+(\d+):")
+
+
+def load_new_tag_types(input_file):
+ """Load new tag types from JSON, converting keys to integers."""
+ with open(input_file, "r") as f:
+ raw = json.load(f)
+ return {int(k): v for k, v in raw.items()}
+
+
+def parse_current_definitions(content):
+ """Extract current fallback definitions from tag_types.py content."""
+ match = FALLBACK_PATTERN.search(content)
+ if not match:
+ print("Error: Could not find fallback_definitions in tag_types.py")
+ sys.exit(1)
+
+ current_types = {}
+ for line in match.group(2).split("\n"):
+ m = ENTRY_PATTERN.match(line)
+ if m:
+ type_id = int(m.group(1))
+ current_types[type_id] = line.strip()
+
+ return current_types
+
+
+def compute_changes(current_types, new_tag_types):
+ """Compute added, removed, and modified tag types."""
+ added = []
+ removed = []
+ modified = []
+
+ for type_id in sorted(new_tag_types.keys()):
+ if type_id not in current_types:
+ added.append(type_id)
+ else:
+ new_line = f"{type_id}: {json.dumps(new_tag_types[type_id], ensure_ascii=False)},"
+ if new_line != current_types[type_id]:
+ modified.append(type_id)
+
+ for type_id in sorted(current_types.keys()):
+ if type_id not in new_tag_types:
+ removed.append(type_id)
+
+ return added, removed, modified
+
+
+def generate_fallback_content(new_tag_types):
+ """Generate the new fallback_definitions dict content."""
+ lines = []
+ for type_id in sorted(new_tag_types.keys()):
+ type_data = new_tag_types[type_id]
+ line = f" {type_id}: {json.dumps(type_data, ensure_ascii=False)},"
+ lines.append(line)
+ return "\n".join(lines)
+
+
+def update_tag_types_file(content, new_fallback):
+ """Replace fallback_definitions content in tag_types.py."""
+ match = FALLBACK_PATTERN.search(content)
+ if not match:
+ print("Error: Could not find fallback_definitions in tag_types.py")
+ sys.exit(1)
+
+ start = match.start(2)
+ end = match.end(2)
+ return content[:start] + new_fallback + content[end:]
+
+
+def build_summary(added, removed, modified):
+ """Build a human-readable summary of changes."""
+ summary = []
+ if added:
+ ids = ", ".join(map(str, added[:5]))
+ suffix = "..." if len(added) > 5 else ""
+ summary.append(f"Added: {len(added)} types ({ids}{suffix})")
+ if removed:
+ ids = ", ".join(map(str, removed[:5]))
+ suffix = "..." if len(removed) > 5 else ""
+ summary.append(f"Removed: {len(removed)} types ({ids}{suffix})")
+ if modified:
+ ids = ", ".join(map(str, modified[:5]))
+ suffix = "..." if len(modified) > 5 else ""
+ summary.append(f"Modified: {len(modified)} types ({ids}{suffix})")
+ return summary
+
+
+def set_github_output(changed, summary):
+ """Set GitHub Actions step outputs."""
+ github_output = os.environ.get("GITHUB_OUTPUT")
+ if not github_output:
+ return
+
+ with open(github_output, "a") as f:
+ f.write(f"changed={'true' if changed else 'false'}\n")
+ if summary:
+ f.write(f"summary={'|'.join(summary)}\n")
+
+
+def main():
+ """Generate updated tag_types.py from fetched definitions."""
+ input_file = sys.argv[1] if len(sys.argv) > 1 else "new_tag_types.json"
+
+ new_tag_types = load_new_tag_types(input_file)
+
+ with open(TAG_TYPES_PATH, "r") as f:
+ content = f.read()
+
+ current_types = parse_current_definitions(content)
+
+ print(f"Current definitions: {len(current_types)} types")
+ print(f"New definitions: {len(new_tag_types)} types")
+
+ added, removed, modified = compute_changes(current_types, new_tag_types)
+ changed = bool(added or removed or modified)
+
+ new_fallback = generate_fallback_content(new_tag_types)
+ new_content = update_tag_types_file(content, new_fallback)
+
+ with open(TAG_TYPES_PATH, "w") as f:
+ f.write(new_content)
+
+ summary = build_summary(added, removed, modified)
+
+ if changed:
+ print("CHANGED=true")
+ print(f"SUMMARY={'|'.join(summary)}")
+ else:
+ print("CHANGED=false")
+ print("No changes detected")
+
+ set_github_output(changed, summary)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tests/drawcustom/arc_test.py b/tests/drawcustom/arc_test.py
index 21c23c2..beb052f 100644
--- a/tests/drawcustom/arc_test.py
+++ b/tests/drawcustom/arc_test.py
@@ -31,7 +31,7 @@ async def test_arc_basic(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(ARC_IMG_PATH, 'arc_basic.png'))
+ example_img = Image.open(os.path.join(ARC_IMG_PATH, 'arc_basic.jpg'))
assert images_equal(generated_img, example_img), "Basic arc rendering failed"
@pytest.mark.asyncio
@@ -56,5 +56,5 @@ async def test_pie_slice_basic(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(ARC_IMG_PATH, 'pie_slice_basic.png'))
+ example_img = Image.open(os.path.join(ARC_IMG_PATH, 'pie_slice_basic.jpg'))
assert images_equal(generated_img, example_img), "Basic pie slice rendering failed"
\ No newline at end of file
diff --git a/tests/drawcustom/circle_test.py b/tests/drawcustom/circle_test.py
index ffe8154..0a13080 100644
--- a/tests/drawcustom/circle_test.py
+++ b/tests/drawcustom/circle_test.py
@@ -31,7 +31,7 @@ async def test_circle_filled(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(CIRCLE_IMG_PATH, 'circle_filled.png'))
+ example_img = Image.open(os.path.join(CIRCLE_IMG_PATH, 'circle_filled.jpg'))
assert images_equal(generated_img, example_img), "Basic filled circle rendering failed"
@pytest.mark.asyncio
@@ -55,7 +55,7 @@ async def test_circle_outline(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(CIRCLE_IMG_PATH, 'circle_outline.png'))
+ example_img = Image.open(os.path.join(CIRCLE_IMG_PATH, 'circle_outline.jpg'))
assert images_equal(generated_img, example_img), "Basic outline circle rendering failed"
@pytest.mark.asyncio
@@ -80,5 +80,5 @@ async def test_circle_percentage(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(CIRCLE_IMG_PATH, 'circle_percentage.png'))
+ example_img = Image.open(os.path.join(CIRCLE_IMG_PATH, 'circle_percentage.jpg'))
assert images_equal(generated_img, example_img), "Basic filled circle rendering failed"
diff --git a/tests/drawcustom/common_test.py b/tests/drawcustom/common_test.py
index a991340..ec55a69 100644
--- a/tests/drawcustom/common_test.py
+++ b/tests/drawcustom/common_test.py
@@ -30,7 +30,7 @@ async def test_multiple_elements(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(COMMON_IMG_PATH, 'multiple_elements.png'))
+ example_img = Image.open(os.path.join(COMMON_IMG_PATH, 'multiple_elements.jpg'))
assert images_equal(generated_img, example_img), "Multiple elements drawing failed"
@pytest.mark.asyncio
@@ -56,7 +56,7 @@ async def test_rotation(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(COMMON_IMG_PATH, 'rotated.png'))
+ example_img = Image.open(os.path.join(COMMON_IMG_PATH, 'rotated.jpg'))
assert images_equal(generated_img, example_img), "rotated elements drawing failed"
@pytest.mark.asyncio
@@ -76,7 +76,7 @@ async def test_oversize_elements(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(COMMON_IMG_PATH, 'oversize_elements.png'))
+ example_img = Image.open(os.path.join(COMMON_IMG_PATH, 'oversize_elements.jpg'))
assert images_equal(generated_img, example_img), "Oversize elements drawing failed"
@pytest.mark.asyncio
@@ -97,7 +97,7 @@ async def test_overlapping_elements(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(COMMON_IMG_PATH, 'overlapping_elements.png'))
+ example_img = Image.open(os.path.join(COMMON_IMG_PATH, 'overlapping_elements.jpg'))
assert images_equal(generated_img, example_img), "Overlapping elements drawing failed"
@pytest.mark.asyncio
@@ -117,5 +117,5 @@ async def test_negative_coordinates(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(COMMON_IMG_PATH, 'negative_coordinates.png'))
+ example_img = Image.open(os.path.join(COMMON_IMG_PATH, 'negative_coordinates.jpg'))
assert images_equal(generated_img, example_img), "Negative coordinate elements drawing failed"
diff --git a/tests/drawcustom/conftest.py b/tests/drawcustom/conftest.py
index f53cb65..cdeacc7 100644
--- a/tests/drawcustom/conftest.py
+++ b/tests/drawcustom/conftest.py
@@ -167,7 +167,7 @@ def images_equal(img1, img2):
def save_image(image_bytes):
"""Save image for debugging."""
- img_path = os.path.join(BASE_IMG_PATH, 'rename_me.png')
+ img_path = os.path.join(BASE_IMG_PATH, 'rename_me.jpg')
with open(img_path, 'wb') as f:
f.write(image_bytes)
diff --git a/tests/drawcustom/debug_grid_test.py b/tests/drawcustom/debug_grid_test.py
index 89d2075..dcbf9bf 100644
--- a/tests/drawcustom/debug_grid_test.py
+++ b/tests/drawcustom/debug_grid_test.py
@@ -26,7 +26,7 @@ async def test_debug_grid_basic(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(DEBUG_GRID_IMG_PATH, 'debug_grid_basic.png'))
+ example_img = Image.open(os.path.join(DEBUG_GRID_IMG_PATH, 'debug_grid_basic.jpg'))
assert images_equal(generated_img, example_img), "Basic debug grid rendering failed"
@pytest.mark.asyncio
@@ -47,7 +47,7 @@ async def test_debug_grid_custom_spacing(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(DEBUG_GRID_IMG_PATH, 'debug_grid_custom_spacing.png'))
+ example_img = Image.open(os.path.join(DEBUG_GRID_IMG_PATH, 'debug_grid_custom_spacing.jpg'))
assert images_equal(generated_img, example_img), "Custom spacing debug grid rendering failed"
@pytest.mark.asyncio
@@ -71,7 +71,7 @@ async def test_debug_grid_solid(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(DEBUG_GRID_IMG_PATH, 'debug_grid_solid.png'))
+ example_img = Image.open(os.path.join(DEBUG_GRID_IMG_PATH, 'debug_grid_solid.jpg'))
assert images_equal(generated_img, example_img), "Solid debug grid rendering failed"
@pytest.mark.asyncio
@@ -91,5 +91,5 @@ async def test_debug_grid_without_labels(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(DEBUG_GRID_IMG_PATH, 'debug_grid_without_labels.png'))
+ example_img = Image.open(os.path.join(DEBUG_GRID_IMG_PATH, 'debug_grid_without_labels.jpg'))
assert images_equal(generated_img, example_img), "Debug grid without labels rendering failed"
\ No newline at end of file
diff --git a/tests/drawcustom/ellipse_test.py b/tests/drawcustom/ellipse_test.py
index 44d1e5d..beb2955 100644
--- a/tests/drawcustom/ellipse_test.py
+++ b/tests/drawcustom/ellipse_test.py
@@ -32,7 +32,7 @@ async def test_circle_ellipse(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(ELLIPSE_IMG_PATH, 'ellipse_drawing.png'))
+ example_img = Image.open(os.path.join(ELLIPSE_IMG_PATH, 'ellipse_drawing.jpg'))
assert images_equal(generated_img, example_img), "Basic ellipse drawing failed"
@pytest.mark.asyncio
@@ -58,5 +58,5 @@ async def test_circle_ellipse_percentage(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(ELLIPSE_IMG_PATH, 'ellipse_drawing_percentage.png'))
+ example_img = Image.open(os.path.join(ELLIPSE_IMG_PATH, 'ellipse_drawing_percentage.jpg'))
assert images_equal(generated_img, example_img), "Basic ellipse drawing failed"
\ No newline at end of file
diff --git a/tests/drawcustom/icon_test.py b/tests/drawcustom/icon_test.py
index 69264b1..ee1edec 100644
--- a/tests/drawcustom/icon_test.py
+++ b/tests/drawcustom/icon_test.py
@@ -30,5 +30,5 @@
#
# generated_img = Image.open(BytesIO(image_data))
# save_image(image_data)
-# example_img = Image.open(os.path.join(ICON_IMG_PATH, 'icon_basic.png'))
+# example_img = Image.open(os.path.join(ICON_IMG_PATH, 'icon_basic.jpg'))
# assert images_equal(generated_img, example_img), "Basic icon rendering failed"
\ No newline at end of file
diff --git a/tests/drawcustom/line_test.py b/tests/drawcustom/line_test.py
index 12943d8..02a5a82 100644
--- a/tests/drawcustom/line_test.py
+++ b/tests/drawcustom/line_test.py
@@ -30,7 +30,7 @@ async def test_line_basic(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(LINE_IMG_PATH, 'line_basic.png'))
+ example_img = Image.open(os.path.join(LINE_IMG_PATH, 'line_basic.jpg'))
assert images_equal(generated_img, example_img), "Basic line rendering failed"
@pytest.mark.asyncio
@@ -55,7 +55,7 @@ async def test_line_custom(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(LINE_IMG_PATH, 'line_custom.png'))
+ example_img = Image.open(os.path.join(LINE_IMG_PATH, 'line_custom.jpg'))
assert images_equal(generated_img, example_img), "Custom line rendering failed"
@pytest.mark.asyncio
@@ -80,7 +80,7 @@ async def test_dashed_line_basic(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(LINE_IMG_PATH, 'dashed_line_basic.png'))
+ example_img = Image.open(os.path.join(LINE_IMG_PATH, 'dashed_line_basic.jpg'))
assert images_equal(generated_img, example_img), "Basic dashed line rendering failed"
@pytest.mark.asyncio
@@ -107,7 +107,7 @@ async def test_dashed_line_custom_lengths(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(LINE_IMG_PATH, 'dashed_line_custom_lengths.png'))
+ example_img = Image.open(os.path.join(LINE_IMG_PATH, 'dashed_line_custom_lengths.jpg'))
assert images_equal(generated_img, example_img), "Custom dashed line rendering failed"
@pytest.mark.asyncio
@@ -131,7 +131,7 @@ async def test_dashed_line_basic_vertical(image_gen, mock_tag_info):
return_value=mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(LINE_IMG_PATH, 'dashed_line_vertical.png'))
+ example_img = Image.open(os.path.join(LINE_IMG_PATH, 'dashed_line_vertical.jpg'))
assert images_equal(generated_img, example_img), "Vertical dashed line rendering failed"
@pytest.mark.asyncio
@@ -158,5 +158,5 @@ async def test_dashed_line_diagonal(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(LINE_IMG_PATH, 'dashed_line_diagonal.png'))
+ example_img = Image.open(os.path.join(LINE_IMG_PATH, 'dashed_line_diagonal.jpg'))
assert images_equal(generated_img, example_img), "Dashed line diagonal rendering failed"
\ No newline at end of file
diff --git a/tests/drawcustom/polygon_test.py b/tests/drawcustom/polygon_test.py
index af2eec1..2d7e7f0 100644
--- a/tests/drawcustom/polygon_test.py
+++ b/tests/drawcustom/polygon_test.py
@@ -26,7 +26,7 @@ async def test_polygon_basic(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(POLYGON_IMG_PATH, 'polygon_basic.png'))
+ example_img = Image.open(os.path.join(POLYGON_IMG_PATH, 'polygon_basic.jpg'))
assert images_equal(generated_img, example_img), "Basic polygon rendering failed"
@pytest.mark.asyncio
@@ -46,5 +46,5 @@ async def test_polygon_filled(image_gen, mock_tag_info):
return_value=mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(POLYGON_IMG_PATH, 'polygon_filled.png'))
+ example_img = Image.open(os.path.join(POLYGON_IMG_PATH, 'polygon_filled.jpg'))
assert images_equal(generated_img, example_img), "Filled polygon rendering failed"
\ No newline at end of file
diff --git a/tests/drawcustom/progress_bar_test.py b/tests/drawcustom/progress_bar_test.py
index 5c52abf..7443875 100644
--- a/tests/drawcustom/progress_bar_test.py
+++ b/tests/drawcustom/progress_bar_test.py
@@ -34,7 +34,7 @@ async def test_basic_progress_bar(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'progress_bar.png'))
+ example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'progress_bar.jpg'))
assert images_equal(generated_img, example_img), "Basic progress bar drawing failed"
@pytest.mark.asyncio
@@ -62,7 +62,7 @@ async def test_progress_bar_zero_progress(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'progress_bar_zero.png'))
+ example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'progress_bar_zero.jpg'))
assert images_equal(generated_img, example_img), "Basic progress bar drawing failed"
@pytest.mark.asyncio
@@ -91,7 +91,7 @@ async def test_progress_bar_full(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'progress_bar_full.png'))
+ example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'progress_bar_full.jpg'))
assert images_equal(generated_img, example_img), "Full progress bar drawing failed"
@pytest.mark.asyncio
@@ -119,7 +119,7 @@ async def test_progress_bar_negative_progress(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'progress_bar_zero.png'))
+ example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'progress_bar_zero.jpg'))
assert images_equal(generated_img, example_img), "Progress bar with negative percentage drawing failed"
@pytest.mark.asyncio
@@ -148,7 +148,7 @@ async def test_progress_bar_over_full(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'progress_bar_full.png'))
+ example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'progress_bar_full.jpg'))
assert images_equal(generated_img, example_img), "Over full progress bar drawing failed"
@pytest.mark.asyncio
@@ -175,5 +175,5 @@ async def test_basic_progress_bar_percentage(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'progress_bar_percentage.png'))
+ example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'progress_bar_percentage.jpg'))
assert images_equal(generated_img, example_img), "Basic progress bar with percentage drawing failed"
\ No newline at end of file
diff --git a/tests/drawcustom/qr_code_test.py b/tests/drawcustom/qr_code_test.py
index b317d89..8262b33 100644
--- a/tests/drawcustom/qr_code_test.py
+++ b/tests/drawcustom/qr_code_test.py
@@ -32,7 +32,7 @@ async def test_basic_qr_code(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'qr_code.png'))
+ example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'qr_code.jpg'))
assert images_equal(generated_img, example_img), "Basic qr code drawing failed"
@pytest.mark.asyncio
@@ -59,7 +59,7 @@ async def test_long_qr_code(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'qr_code_long.png'))
+ example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'qr_code_long.jpg'))
assert images_equal(generated_img, example_img), "Long qr code drawing failed"
@pytest.mark.asyncio
@@ -85,5 +85,5 @@ async def test_basic_qr_code_percentage(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'qr_code_percentage.png'))
+ example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'qr_code_percentage.jpg'))
assert images_equal(generated_img, example_img), "Basic qr code drawing with percentage failed"
\ No newline at end of file
diff --git a/tests/drawcustom/rectangle_pattern_test.py b/tests/drawcustom/rectangle_pattern_test.py
index e066e89..b775823 100644
--- a/tests/drawcustom/rectangle_pattern_test.py
+++ b/tests/drawcustom/rectangle_pattern_test.py
@@ -36,7 +36,7 @@ async def test_rectangle_pattern(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(RECTANGLE_IMG_PATH, 'rectangle_pattern.png'))
+ example_img = Image.open(os.path.join(RECTANGLE_IMG_PATH, 'rectangle_pattern.jpg'))
assert images_equal(generated_img, example_img), "Rectangle pattern rendering failed"
@pytest.mark.asyncio
@@ -68,7 +68,7 @@ async def test_rectangle_pattern_rounded_corners(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(RECTANGLE_IMG_PATH, 'rectangle_pattern_rounded_corners.png'))
+ example_img = Image.open(os.path.join(RECTANGLE_IMG_PATH, 'rectangle_pattern_rounded_corners.jpg'))
assert images_equal(generated_img, example_img), "Rounded corner rectangle pattern rendering failed"
@pytest.mark.asyncio
@@ -98,5 +98,5 @@ async def test_rectangle_pattern(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(BASE_IMG_PATH, 'blank.png'))
+ example_img = Image.open(os.path.join(BASE_IMG_PATH, 'blank.jpg'))
assert images_equal(generated_img, example_img), "Rounded corner rectangle pattern rendering failed"
\ No newline at end of file
diff --git a/tests/drawcustom/rectangle_test.py b/tests/drawcustom/rectangle_test.py
index 7491f4b..00a35e3 100644
--- a/tests/drawcustom/rectangle_test.py
+++ b/tests/drawcustom/rectangle_test.py
@@ -32,7 +32,7 @@ async def test_rectangle_filled(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(RECTANGLE_IMG_PATH, 'rectangle_filled.png'))
+ example_img = Image.open(os.path.join(RECTANGLE_IMG_PATH, 'rectangle_filled.jpg'))
assert images_equal(generated_img, example_img), "Filled rectangle rendering failed"
@pytest.mark.asyncio
@@ -57,7 +57,7 @@ async def test_rectangle_outline(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(RECTANGLE_IMG_PATH, 'rectangle_outline.png'))
+ example_img = Image.open(os.path.join(RECTANGLE_IMG_PATH, 'rectangle_outline.jpg'))
assert images_equal(generated_img, example_img), "Outlined rectangle rendering failed"
@pytest.mark.asyncio
@@ -85,5 +85,5 @@ async def test_rectangle_rounded_corners(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(RECTANGLE_IMG_PATH, 'rectangle_rounded_corners.png'))
+ example_img = Image.open(os.path.join(RECTANGLE_IMG_PATH, 'rectangle_rounded_corners.jpg'))
assert images_equal(generated_img, example_img), "Rounded corner rectangle rendering failed"
\ No newline at end of file
diff --git a/tests/drawcustom/test_images/arc/arc_basic.png b/tests/drawcustom/test_images/arc/arc_basic.jpg
similarity index 100%
rename from tests/drawcustom/test_images/arc/arc_basic.png
rename to tests/drawcustom/test_images/arc/arc_basic.jpg
diff --git a/tests/drawcustom/test_images/arc/pie_slice_basic.png b/tests/drawcustom/test_images/arc/pie_slice_basic.jpg
similarity index 100%
rename from tests/drawcustom/test_images/arc/pie_slice_basic.png
rename to tests/drawcustom/test_images/arc/pie_slice_basic.jpg
diff --git a/tests/drawcustom/test_images/blank.png b/tests/drawcustom/test_images/blank.jpg
similarity index 100%
rename from tests/drawcustom/test_images/blank.png
rename to tests/drawcustom/test_images/blank.jpg
diff --git a/tests/drawcustom/test_images/circle/circle_filled.png b/tests/drawcustom/test_images/circle/circle_filled.jpg
similarity index 100%
rename from tests/drawcustom/test_images/circle/circle_filled.png
rename to tests/drawcustom/test_images/circle/circle_filled.jpg
diff --git a/tests/drawcustom/test_images/circle/circle_outline.png b/tests/drawcustom/test_images/circle/circle_outline.jpg
similarity index 100%
rename from tests/drawcustom/test_images/circle/circle_outline.png
rename to tests/drawcustom/test_images/circle/circle_outline.jpg
diff --git a/tests/drawcustom/test_images/circle/circle_percentage.png b/tests/drawcustom/test_images/circle/circle_percentage.jpg
similarity index 100%
rename from tests/drawcustom/test_images/circle/circle_percentage.png
rename to tests/drawcustom/test_images/circle/circle_percentage.jpg
diff --git a/tests/drawcustom/test_images/common/multiple_elements.png b/tests/drawcustom/test_images/common/multiple_elements.jpg
similarity index 100%
rename from tests/drawcustom/test_images/common/multiple_elements.png
rename to tests/drawcustom/test_images/common/multiple_elements.jpg
diff --git a/tests/drawcustom/test_images/common/negative_coordinates.png b/tests/drawcustom/test_images/common/negative_coordinates.jpg
similarity index 100%
rename from tests/drawcustom/test_images/common/negative_coordinates.png
rename to tests/drawcustom/test_images/common/negative_coordinates.jpg
diff --git a/tests/drawcustom/test_images/common/overlapping_elements.jpg b/tests/drawcustom/test_images/common/overlapping_elements.jpg
new file mode 100644
index 0000000..a8eab05
Binary files /dev/null and b/tests/drawcustom/test_images/common/overlapping_elements.jpg differ
diff --git a/tests/drawcustom/test_images/common/overlapping_elements.png b/tests/drawcustom/test_images/common/overlapping_elements.png
deleted file mode 100644
index 4c213b4..0000000
Binary files a/tests/drawcustom/test_images/common/overlapping_elements.png and /dev/null differ
diff --git a/tests/drawcustom/test_images/common/oversize_elements.png b/tests/drawcustom/test_images/common/oversize_elements.jpg
similarity index 100%
rename from tests/drawcustom/test_images/common/oversize_elements.png
rename to tests/drawcustom/test_images/common/oversize_elements.jpg
diff --git a/tests/drawcustom/test_images/common/rotated.png b/tests/drawcustom/test_images/common/rotated.jpg
similarity index 100%
rename from tests/drawcustom/test_images/common/rotated.png
rename to tests/drawcustom/test_images/common/rotated.jpg
diff --git a/tests/drawcustom/test_images/debug_grid/debug_grid_basic.png b/tests/drawcustom/test_images/debug_grid/debug_grid_basic.jpg
similarity index 100%
rename from tests/drawcustom/test_images/debug_grid/debug_grid_basic.png
rename to tests/drawcustom/test_images/debug_grid/debug_grid_basic.jpg
diff --git a/tests/drawcustom/test_images/debug_grid/debug_grid_custom_spacing.png b/tests/drawcustom/test_images/debug_grid/debug_grid_custom_spacing.jpg
similarity index 100%
rename from tests/drawcustom/test_images/debug_grid/debug_grid_custom_spacing.png
rename to tests/drawcustom/test_images/debug_grid/debug_grid_custom_spacing.jpg
diff --git a/tests/drawcustom/test_images/debug_grid/debug_grid_solid.png b/tests/drawcustom/test_images/debug_grid/debug_grid_solid.jpg
similarity index 100%
rename from tests/drawcustom/test_images/debug_grid/debug_grid_solid.png
rename to tests/drawcustom/test_images/debug_grid/debug_grid_solid.jpg
diff --git a/tests/drawcustom/test_images/debug_grid/debug_grid_without_labels.png b/tests/drawcustom/test_images/debug_grid/debug_grid_without_labels.jpg
similarity index 100%
rename from tests/drawcustom/test_images/debug_grid/debug_grid_without_labels.png
rename to tests/drawcustom/test_images/debug_grid/debug_grid_without_labels.jpg
diff --git a/tests/drawcustom/test_images/ellipse/ellipse_drawing.png b/tests/drawcustom/test_images/ellipse/ellipse_drawing.jpg
similarity index 100%
rename from tests/drawcustom/test_images/ellipse/ellipse_drawing.png
rename to tests/drawcustom/test_images/ellipse/ellipse_drawing.jpg
diff --git a/tests/drawcustom/test_images/ellipse/ellipse_drawing_percentage.png b/tests/drawcustom/test_images/ellipse/ellipse_drawing_percentage.jpg
similarity index 100%
rename from tests/drawcustom/test_images/ellipse/ellipse_drawing_percentage.png
rename to tests/drawcustom/test_images/ellipse/ellipse_drawing_percentage.jpg
diff --git a/tests/drawcustom/test_images/line/dashed_line_basic.png b/tests/drawcustom/test_images/line/dashed_line_basic.jpg
similarity index 100%
rename from tests/drawcustom/test_images/line/dashed_line_basic.png
rename to tests/drawcustom/test_images/line/dashed_line_basic.jpg
diff --git a/tests/drawcustom/test_images/line/dashed_line_custom_lengths.png b/tests/drawcustom/test_images/line/dashed_line_custom_lengths.jpg
similarity index 100%
rename from tests/drawcustom/test_images/line/dashed_line_custom_lengths.png
rename to tests/drawcustom/test_images/line/dashed_line_custom_lengths.jpg
diff --git a/tests/drawcustom/test_images/line/dashed_line_diagonal.png b/tests/drawcustom/test_images/line/dashed_line_diagonal.jpg
similarity index 100%
rename from tests/drawcustom/test_images/line/dashed_line_diagonal.png
rename to tests/drawcustom/test_images/line/dashed_line_diagonal.jpg
diff --git a/tests/drawcustom/test_images/line/dashed_line_vertical.png b/tests/drawcustom/test_images/line/dashed_line_vertical.jpg
similarity index 100%
rename from tests/drawcustom/test_images/line/dashed_line_vertical.png
rename to tests/drawcustom/test_images/line/dashed_line_vertical.jpg
diff --git a/tests/drawcustom/test_images/line/line_basic.png b/tests/drawcustom/test_images/line/line_basic.jpg
similarity index 100%
rename from tests/drawcustom/test_images/line/line_basic.png
rename to tests/drawcustom/test_images/line/line_basic.jpg
diff --git a/tests/drawcustom/test_images/line/line_custom.png b/tests/drawcustom/test_images/line/line_custom.jpg
similarity index 100%
rename from tests/drawcustom/test_images/line/line_custom.png
rename to tests/drawcustom/test_images/line/line_custom.jpg
diff --git a/tests/drawcustom/test_images/polygon/polygon_basic.png b/tests/drawcustom/test_images/polygon/polygon_basic.jpg
similarity index 100%
rename from tests/drawcustom/test_images/polygon/polygon_basic.png
rename to tests/drawcustom/test_images/polygon/polygon_basic.jpg
diff --git a/tests/drawcustom/test_images/polygon/polygon_filled.png b/tests/drawcustom/test_images/polygon/polygon_filled.jpg
similarity index 100%
rename from tests/drawcustom/test_images/polygon/polygon_filled.png
rename to tests/drawcustom/test_images/polygon/polygon_filled.jpg
diff --git a/tests/drawcustom/test_images/progress_bar/progress_bar.png b/tests/drawcustom/test_images/progress_bar/progress_bar.jpg
similarity index 100%
rename from tests/drawcustom/test_images/progress_bar/progress_bar.png
rename to tests/drawcustom/test_images/progress_bar/progress_bar.jpg
diff --git a/tests/drawcustom/test_images/progress_bar/progress_bar_full.png b/tests/drawcustom/test_images/progress_bar/progress_bar_full.jpg
similarity index 100%
rename from tests/drawcustom/test_images/progress_bar/progress_bar_full.png
rename to tests/drawcustom/test_images/progress_bar/progress_bar_full.jpg
diff --git a/tests/drawcustom/test_images/progress_bar/progress_bar_percentage.png b/tests/drawcustom/test_images/progress_bar/progress_bar_percentage.jpg
similarity index 100%
rename from tests/drawcustom/test_images/progress_bar/progress_bar_percentage.png
rename to tests/drawcustom/test_images/progress_bar/progress_bar_percentage.jpg
diff --git a/tests/drawcustom/test_images/progress_bar/progress_bar_zero.png b/tests/drawcustom/test_images/progress_bar/progress_bar_zero.jpg
similarity index 100%
rename from tests/drawcustom/test_images/progress_bar/progress_bar_zero.png
rename to tests/drawcustom/test_images/progress_bar/progress_bar_zero.jpg
diff --git a/tests/drawcustom/test_images/qr_code/qr_code.png b/tests/drawcustom/test_images/qr_code/qr_code.jpg
similarity index 100%
rename from tests/drawcustom/test_images/qr_code/qr_code.png
rename to tests/drawcustom/test_images/qr_code/qr_code.jpg
diff --git a/tests/drawcustom/test_images/qr_code/qr_code_long.png b/tests/drawcustom/test_images/qr_code/qr_code_long.jpg
similarity index 100%
rename from tests/drawcustom/test_images/qr_code/qr_code_long.png
rename to tests/drawcustom/test_images/qr_code/qr_code_long.jpg
diff --git a/tests/drawcustom/test_images/qr_code/qr_code_percentage.png b/tests/drawcustom/test_images/qr_code/qr_code_percentage.jpg
similarity index 100%
rename from tests/drawcustom/test_images/qr_code/qr_code_percentage.png
rename to tests/drawcustom/test_images/qr_code/qr_code_percentage.jpg
diff --git a/tests/drawcustom/test_images/rectangle/rectangle_filled.png b/tests/drawcustom/test_images/rectangle/rectangle_filled.jpg
similarity index 100%
rename from tests/drawcustom/test_images/rectangle/rectangle_filled.png
rename to tests/drawcustom/test_images/rectangle/rectangle_filled.jpg
diff --git a/tests/drawcustom/test_images/rectangle/rectangle_outline.png b/tests/drawcustom/test_images/rectangle/rectangle_outline.jpg
similarity index 100%
rename from tests/drawcustom/test_images/rectangle/rectangle_outline.png
rename to tests/drawcustom/test_images/rectangle/rectangle_outline.jpg
diff --git a/tests/drawcustom/test_images/rectangle/rectangle_rounded_corners.png b/tests/drawcustom/test_images/rectangle/rectangle_rounded_corners.jpg
similarity index 100%
rename from tests/drawcustom/test_images/rectangle/rectangle_rounded_corners.png
rename to tests/drawcustom/test_images/rectangle/rectangle_rounded_corners.jpg
diff --git a/tests/drawcustom/test_images/rectangle_pattern/rectangle_pattern.png b/tests/drawcustom/test_images/rectangle_pattern/rectangle_pattern.jpg
similarity index 100%
rename from tests/drawcustom/test_images/rectangle_pattern/rectangle_pattern.png
rename to tests/drawcustom/test_images/rectangle_pattern/rectangle_pattern.jpg
diff --git a/tests/drawcustom/test_images/rectangle_pattern/rectangle_pattern_rounded_corners.png b/tests/drawcustom/test_images/rectangle_pattern/rectangle_pattern_rounded_corners.jpg
similarity index 100%
rename from tests/drawcustom/test_images/rectangle_pattern/rectangle_pattern_rounded_corners.png
rename to tests/drawcustom/test_images/rectangle_pattern/rectangle_pattern_rounded_corners.jpg
diff --git a/tests/drawcustom/test_images/text/large_font.png b/tests/drawcustom/test_images/text/large_font.jpg
similarity index 100%
rename from tests/drawcustom/test_images/text/large_font.png
rename to tests/drawcustom/test_images/text/large_font.jpg
diff --git a/tests/drawcustom/test_images/text/small_font.png b/tests/drawcustom/test_images/text/small_font.jpg
similarity index 100%
rename from tests/drawcustom/test_images/text/small_font.png
rename to tests/drawcustom/test_images/text/small_font.jpg
diff --git a/tests/drawcustom/test_images/text/text_anchors.png b/tests/drawcustom/test_images/text/text_anchors.jpg
similarity index 100%
rename from tests/drawcustom/test_images/text/text_anchors.png
rename to tests/drawcustom/test_images/text/text_anchors.jpg
diff --git a/tests/drawcustom/test_images/text/text_basic.png b/tests/drawcustom/test_images/text/text_basic.jpg
similarity index 100%
rename from tests/drawcustom/test_images/text/text_basic.png
rename to tests/drawcustom/test_images/text/text_basic.jpg
diff --git a/tests/drawcustom/test_images/text/text_color_markup.png b/tests/drawcustom/test_images/text/text_color_markup.jpg
similarity index 100%
rename from tests/drawcustom/test_images/text/text_color_markup.png
rename to tests/drawcustom/test_images/text/text_color_markup.jpg
diff --git a/tests/drawcustom/test_images/text/text_mixed_fonts.png b/tests/drawcustom/test_images/text/text_mixed_fonts.jpg
similarity index 100%
rename from tests/drawcustom/test_images/text/text_mixed_fonts.png
rename to tests/drawcustom/test_images/text/text_mixed_fonts.jpg
diff --git a/tests/drawcustom/test_images/text/text_percentage.png b/tests/drawcustom/test_images/text/text_percentage.jpg
similarity index 100%
rename from tests/drawcustom/test_images/text/text_percentage.png
rename to tests/drawcustom/test_images/text/text_percentage.jpg
diff --git a/tests/drawcustom/test_images/text/text_special_chars.png b/tests/drawcustom/test_images/text/text_special_chars.jpg
similarity index 100%
rename from tests/drawcustom/test_images/text/text_special_chars.png
rename to tests/drawcustom/test_images/text/text_special_chars.jpg
diff --git a/tests/drawcustom/test_images/text/text_truncate.png b/tests/drawcustom/test_images/text/text_truncate.jpg
similarity index 100%
rename from tests/drawcustom/test_images/text/text_truncate.png
rename to tests/drawcustom/test_images/text/text_truncate.jpg
diff --git a/tests/drawcustom/test_images/text/text_wrapping.png b/tests/drawcustom/test_images/text/text_wrapping.jpg
similarity index 100%
rename from tests/drawcustom/test_images/text/text_wrapping.png
rename to tests/drawcustom/test_images/text/text_wrapping.jpg
diff --git a/tests/drawcustom/test_images/text/text_wrapping_anchor.png b/tests/drawcustom/test_images/text/text_wrapping_anchor.jpg
similarity index 100%
rename from tests/drawcustom/test_images/text/text_wrapping_anchor.png
rename to tests/drawcustom/test_images/text/text_wrapping_anchor.jpg
diff --git a/tests/drawcustom/test_images/text_multiline/multiline_empty_line.png b/tests/drawcustom/test_images/text_multiline/multiline_empty_line.jpg
similarity index 100%
rename from tests/drawcustom/test_images/text_multiline/multiline_empty_line.png
rename to tests/drawcustom/test_images/text_multiline/multiline_empty_line.jpg
diff --git a/tests/drawcustom/test_images/text_multiline/text_multiline.png b/tests/drawcustom/test_images/text_multiline/text_multiline.jpg
similarity index 100%
rename from tests/drawcustom/test_images/text_multiline/text_multiline.png
rename to tests/drawcustom/test_images/text_multiline/text_multiline.jpg
diff --git a/tests/drawcustom/test_images/text_multiline/text_multiline_delimiter.png b/tests/drawcustom/test_images/text_multiline/text_multiline_delimiter.jpg
similarity index 100%
rename from tests/drawcustom/test_images/text_multiline/text_multiline_delimiter.png
rename to tests/drawcustom/test_images/text_multiline/text_multiline_delimiter.jpg
diff --git a/tests/drawcustom/test_images/text_multiline/text_multiline_delimiter_and_newline.png b/tests/drawcustom/test_images/text_multiline/text_multiline_delimiter_and_newline.jpg
similarity index 100%
rename from tests/drawcustom/test_images/text_multiline/text_multiline_delimiter_and_newline.png
rename to tests/drawcustom/test_images/text_multiline/text_multiline_delimiter_and_newline.jpg
diff --git a/tests/drawcustom/text_multiline_test.py b/tests/drawcustom/text_multiline_test.py
index 6ebbf8e..3b13f6b 100644
--- a/tests/drawcustom/text_multiline_test.py
+++ b/tests/drawcustom/text_multiline_test.py
@@ -32,7 +32,7 @@ async def test_text_multiline_basic(image_gen, mock_tag_info):
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'text_multiline.png'))
+ example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'text_multiline.jpg'))
assert images_equal(generated_img, example_img), "Basic text rendering failed"
@pytest.mark.asyncio
@@ -59,7 +59,7 @@ async def test_text_multiline_delimiter(image_gen, mock_tag_info):
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'text_multiline_delimiter.png'))
+ example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'text_multiline_delimiter.jpg'))
assert images_equal(generated_img, example_img), "Multiline text with delimiter rendering failed"
@pytest.mark.asyncio
@@ -85,7 +85,7 @@ async def test_text_multiline_empty_line(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'multiline_empty_line.png'))
+ example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'multiline_empty_line.jpg'))
assert images_equal(generated_img, example_img), "Multiline text with empty line rendering failed"
@pytest.mark.asyncio
@@ -111,7 +111,7 @@ async def test_text_multiline_delimiter_and_newline(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'text_multiline_delimiter_and_newline.png'))
+ example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'text_multiline_delimiter_and_newline.jpg'))
assert images_equal(generated_img, example_img), "Multiline text with delimiter and newline rendering failed"
# @pytest.mark.asyncio
@@ -142,7 +142,7 @@ async def test_text_multiline_delimiter_and_newline(image_gen, mock_tag_info):
#
# generated_img = Image.open(BytesIO(image_data))
# save_image(image_data)
-# example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'calendar_format.png'))
+# example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'calendar_format.jpg'))
# assert images_equal(generated_img, example_img), "Calendar format multiline rendering failed"
#
# @pytest.mark.asyncio
@@ -172,7 +172,7 @@ async def test_text_multiline_delimiter_and_newline(image_gen, mock_tag_info):
# )
#
# generated_img = Image.open(BytesIO(image_data))
-# example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'multiline_blank_lines.png'))
+# example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'multiline_blank_lines.jpg'))
# assert images_equal(generated_img, example_img), "Multiline text with blank lines rendering failed"
#
# @pytest.mark.asyncio
@@ -202,5 +202,5 @@ async def test_text_multiline_delimiter_and_newline(image_gen, mock_tag_info):
# )
#
# generated_img = Image.open(BytesIO(image_data))
-# example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'multiline_whitespace.png'))
+# example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'multiline_whitespace.jpg'))
# assert images_equal(generated_img, example_img), "Multiline text whitespace handling failed"
\ No newline at end of file
diff --git a/tests/drawcustom/text_test.py b/tests/drawcustom/text_test.py
index b278445..d64e6d6 100644
--- a/tests/drawcustom/text_test.py
+++ b/tests/drawcustom/text_test.py
@@ -31,7 +31,7 @@ async def test_text_basic(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_basic.png'))
+ example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_basic.jpg'))
assert images_equal(generated_img, example_img), "Basic text rendering failed"
@@ -55,7 +55,7 @@ async def test_small_font_size(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'small_font.png'))
+ example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'small_font.jpg'))
assert images_equal(generated_img, example_img), "Small font size rendering failed"
@@ -79,7 +79,7 @@ async def test_large_font_size(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'large_font.png'))
+ example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'large_font.jpg'))
assert images_equal(generated_img, example_img), "Large font size rendering failed"
@@ -104,7 +104,7 @@ async def test_text_wrapping(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_wrapping.png'))
+ example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_wrapping.jpg'))
assert images_equal(generated_img, example_img), "Text wrapping failed"
async def test_text_wrapping_with_anchor(image_gen, mock_tag_info):
@@ -130,7 +130,7 @@ async def test_text_wrapping_with_anchor(image_gen, mock_tag_info):
generated_img = Image.open(BytesIO(image_data))
save_image(image_data)
- example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_wrapping_anchor.png'))
+ example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_wrapping_anchor.jpg'))
assert images_equal(generated_img, example_img), "Text wrapping failed"
@@ -161,7 +161,7 @@ async def test_text_with_special_characters(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_special_chars.png'))
+ example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_special_chars.jpg'))
assert images_equal(generated_img, example_img), "Special characters rendering failed"
@@ -195,7 +195,7 @@ async def test_text_color_markup(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_color_markup.png'))
+ example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_color_markup.jpg'))
assert images_equal(generated_img, example_img), "Color markup rendering failed"
@pytest.mark.asyncio
@@ -220,7 +220,7 @@ async def test_text_percentage(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_percentage.png'))
+ example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_percentage.jpg'))
assert images_equal(generated_img, example_img), "Text with percentage rendering failed"
# @pytest.mark.asyncio
@@ -266,7 +266,7 @@ async def test_text_percentage(image_gen, mock_tag_info):
#
# generated_img = Image.open(BytesIO(image_data))
# save_image(image_data)
-# example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_alignment.png'))
+# example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_alignment.jpg'))
# assert images_equal(generated_img, example_img), "Text alignment failed"
@pytest.mark.asyncio
@@ -311,7 +311,7 @@ async def test_text_anchors(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_anchors.png'))
+ example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_anchors.jpg'))
assert images_equal(generated_img, example_img), "Text anchor points failed"
@pytest.mark.asyncio
@@ -346,7 +346,7 @@ async def test_text_mixed_fonts(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_mixed_fonts.png'))
+ example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_mixed_fonts.jpg'))
assert images_equal(generated_img, example_img), "Mixed fonts rendering failed"
@pytest.mark.asyncio
@@ -370,7 +370,7 @@ async def test_text_empty_string(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(BASE_IMG_PATH, 'blank.png'))
+ example_img = Image.open(os.path.join(BASE_IMG_PATH, 'blank.jpg'))
assert images_equal(generated_img, example_img), "Empty text handling failed"
async def test_text_truncate(image_gen, mock_tag_info):
@@ -395,7 +395,7 @@ async def test_text_truncate(image_gen, mock_tag_info):
image_data = await generate_test_image(image_gen, service_data)
generated_img = Image.open(BytesIO(image_data))
- example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_truncate.png'))
+ example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_truncate.jpg'))
assert images_equal(generated_img, example_img), "Text truncation failed"
# @pytest.mark.asyncio
@@ -445,5 +445,5 @@ async def test_text_truncate(image_gen, mock_tag_info):
# )
#
# generated_img = Image.open(BytesIO(image_data))
-# example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_basic.png'))
+# example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_basic.jpg'))
# assert images_equal(generated_img, example_img), "Basic text rendering failed"
\ No newline at end of file
diff --git a/tests/scripts/test_sync_tag_types.py b/tests/scripts/test_sync_tag_types.py
new file mode 100644
index 0000000..f35eeff
--- /dev/null
+++ b/tests/scripts/test_sync_tag_types.py
@@ -0,0 +1,381 @@
+"""Tests for the tag type sync scripts."""
+
+import json
+import os
+import re
+import textwrap
+from unittest.mock import MagicMock, patch
+
+import pytest
+
+# Add scripts directory to path so we can import the modules
+import sys
+
+REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+sys.path.insert(0, os.path.join(REPO_ROOT, "scripts"))
+
+import fetch_tag_types
+import generate_tag_types
+
+
+# ---------------------------------------------------------------------------
+# Fixtures
+# ---------------------------------------------------------------------------
+
+SAMPLE_TAG_TYPES_PY = textwrap.dedent("""\
+ class Foo:
+ def _load_fallback_types(self):
+ fallback_definitions = {
+ 0: {"version": 4, "name": "M2 1.54\\"", "width": 152, "height": 152},
+ 1: {"version": 5, "name": "M2 2.9\\"", "width": 296, "height": 128},
+ 240: {"version": 2, "name": "SLT\u2010EM007 Segmented", "width": 0, "height": 0},
+ 250: {"version": 1, "name": "ConfigMode", "width": 0, "height": 0},
+ }
+ self._tag_types = {
+ type_id: TagType(type_id, data) for type_id, data in fallback_definitions.items()
+ }
+""")
+
+
+@pytest.fixture
+def tag_types_file(tmp_path):
+ """Write a minimal tag_types.py and return its path."""
+ p = tmp_path / "tag_types.py"
+ p.write_text(SAMPLE_TAG_TYPES_PY)
+ return p
+
+
+@pytest.fixture
+def new_types_json(tmp_path):
+ """Write a new_tag_types.json and return its path."""
+ data = {
+ 0: {"version": 4, "name": 'M2 1.54"', "width": 152, "height": 152},
+ 1: {"version": 5, "name": 'M2 2.9"', "width": 296, "height": 128},
+ 240: {"version": 2, "name": "SLT\u2010EM007 Segmented", "width": 0, "height": 0},
+ 250: {"version": 1, "name": "ConfigMode", "width": 0, "height": 0},
+ }
+ p = tmp_path / "new_tag_types.json"
+ p.write_text(json.dumps(data, indent=2))
+ return p
+
+
+# ---------------------------------------------------------------------------
+# Tests for generate_tag_types – load_new_tag_types
+# ---------------------------------------------------------------------------
+
+class TestLoadNewTagTypes:
+ """Tests for loading and converting JSON tag types."""
+
+ def test_keys_are_integers(self, new_types_json):
+ """JSON string keys must be converted to integers."""
+ result = generate_tag_types.load_new_tag_types(str(new_types_json))
+ assert all(isinstance(k, int) for k in result.keys())
+
+ def test_values_preserved(self, new_types_json):
+ """Tag type data values must be preserved after loading."""
+ result = generate_tag_types.load_new_tag_types(str(new_types_json))
+ assert result[0]["name"] == 'M2 1.54"'
+ assert result[250]["width"] == 0
+
+
+# ---------------------------------------------------------------------------
+# Tests for generate_tag_types – parse_current_definitions
+# ---------------------------------------------------------------------------
+
+class TestParseCurrentDefinitions:
+ """Tests for parsing fallback_definitions from tag_types.py."""
+
+ def test_parses_all_entries(self, tag_types_file):
+ """Should parse all entries from the fallback_definitions block."""
+ content = tag_types_file.read_text()
+ result = generate_tag_types.parse_current_definitions(content)
+ assert len(result) == 4
+ assert set(result.keys()) == {0, 1, 240, 250}
+
+ def test_keys_are_integers(self, tag_types_file):
+ """Parsed keys must be integers."""
+ content = tag_types_file.read_text()
+ result = generate_tag_types.parse_current_definitions(content)
+ assert all(isinstance(k, int) for k in result.keys())
+
+ def test_exits_on_missing_block(self):
+ """Should exit if fallback_definitions block is not found."""
+ with pytest.raises(SystemExit):
+ generate_tag_types.parse_current_definitions("no such block here")
+
+
+# ---------------------------------------------------------------------------
+# Tests for generate_tag_types – compute_changes
+# ---------------------------------------------------------------------------
+
+class TestComputeChanges:
+ """Tests for computing diffs between current and new definitions."""
+
+ def test_no_changes(self):
+ """Identical data should produce no changes."""
+ current = {
+ 0: '0: {"version": 4, "name": "Tag0", "width": 100, "height": 100},',
+ }
+ new = {0: {"version": 4, "name": "Tag0", "width": 100, "height": 100}}
+ added, removed, modified = generate_tag_types.compute_changes(current, new)
+ assert added == []
+ assert removed == []
+ assert modified == []
+
+ def test_added(self):
+ """New type IDs should be detected as added."""
+ current = {}
+ new = {5: {"version": 1, "name": "New", "width": 10, "height": 10}}
+ added, removed, modified = generate_tag_types.compute_changes(current, new)
+ assert added == [5]
+ assert removed == []
+
+ def test_removed(self):
+ """Missing type IDs should be detected as removed."""
+ current = {
+ 5: '5: {"version": 1, "name": "Old", "width": 10, "height": 10},',
+ }
+ new = {}
+ added, removed, modified = generate_tag_types.compute_changes(current, new)
+ assert removed == [5]
+ assert added == []
+
+ def test_modified(self):
+ """Changed values should be detected as modified."""
+ current = {
+ 0: '0: {"version": 1, "name": "Tag0", "width": 100, "height": 100},',
+ }
+ new = {0: {"version": 2, "name": "Tag0", "width": 100, "height": 100}}
+ added, removed, modified = generate_tag_types.compute_changes(current, new)
+ assert modified == [0]
+
+ def test_sorting(self):
+ """Results should be sorted numerically, not lexicographically."""
+ current = {}
+ new = {
+ 100: {"version": 1, "name": "A", "width": 1, "height": 1},
+ 2: {"version": 1, "name": "B", "width": 1, "height": 1},
+ 17: {"version": 1, "name": "C", "width": 1, "height": 1},
+ }
+ added, _, _ = generate_tag_types.compute_changes(current, new)
+ assert added == [2, 17, 100]
+
+
+# ---------------------------------------------------------------------------
+# Tests for generate_tag_types – generate_fallback_content
+# ---------------------------------------------------------------------------
+
+class TestGenerateFallbackContent:
+ """Tests for generating the fallback_definitions dict content."""
+
+ def test_format(self):
+ """Each line should have 12-space indent, type_id, JSON data, and trailing comma."""
+ data = {0: {"version": 1, "name": "Tag", "width": 10, "height": 20}}
+ content = generate_tag_types.generate_fallback_content(data)
+ assert content.startswith(" 0:")
+ assert content.endswith(",")
+
+ def test_sorted_numerically(self):
+ """Entries should be sorted by numeric type_id."""
+ data = {
+ 100: {"version": 1, "name": "A", "width": 1, "height": 1},
+ 2: {"version": 1, "name": "B", "width": 1, "height": 1},
+ 17: {"version": 1, "name": "C", "width": 1, "height": 1},
+ }
+ content = generate_tag_types.generate_fallback_content(data)
+ ids = [
+ int(m.group(1))
+ for line in content.split("\n")
+ if (m := re.match(r"\s+(\d+):", line))
+ ]
+ assert ids == [2, 17, 100]
+
+ def test_unicode_chars_preserved(self):
+ """Unicode characters should be preserved (not escaped) with ensure_ascii=False."""
+ data = {240: {"version": 2, "name": "SLT\u2010EM007", "width": 0, "height": 0}}
+ content = generate_tag_types.generate_fallback_content(data)
+ # ensure_ascii=False preserves the actual Unicode character
+ assert "\u2010" in content
+
+
+# ---------------------------------------------------------------------------
+# Tests for generate_tag_types – update_tag_types_file
+# ---------------------------------------------------------------------------
+
+class TestUpdateTagTypesFile:
+ """Tests for replacing fallback_definitions in file content."""
+
+ def test_replaces_content(self, tag_types_file):
+ """The fallback block should be replaced with new content."""
+ content = tag_types_file.read_text()
+ new_fallback = ' 999: {"version": 1, "name": "New", "width": 1, "height": 1},'
+ result = generate_tag_types.update_tag_types_file(content, new_fallback)
+ assert "999:" in result
+ # Old entries removed
+ assert "250:" not in result
+
+ def test_preserves_surrounding_code(self, tag_types_file):
+ """Code around fallback_definitions should be unchanged."""
+ content = tag_types_file.read_text()
+ new_fallback = ' 999: {"version": 1, "name": "New", "width": 1, "height": 1},'
+ result = generate_tag_types.update_tag_types_file(content, new_fallback)
+ assert "class Foo:" in result
+ assert "self._tag_types" in result
+
+ def test_unicode_in_replacement(self, tag_types_file):
+ """Unicode escape sequences in replacement must not cause regex errors.
+
+ This is the primary bug that was fixed: json.dumps() produces \\uXXXX
+ sequences which re.sub() would interpret as bad regex escapes.
+ """
+ content = tag_types_file.read_text()
+ # This would fail with re.sub() because \u2010 is a bad regex escape
+ new_fallback = ' 240: {"version": 2, "name": "SLT\\u2010EM007", "width": 0, "height": 0},'
+ result = generate_tag_types.update_tag_types_file(content, new_fallback)
+ assert "\\u2010" in result
+
+ def test_exits_on_missing_block(self):
+ """Should exit if fallback_definitions block is not found."""
+ with pytest.raises(SystemExit):
+ generate_tag_types.update_tag_types_file("no such block", "replacement")
+
+
+# ---------------------------------------------------------------------------
+# Tests for generate_tag_types – build_summary
+# ---------------------------------------------------------------------------
+
+class TestBuildSummary:
+ """Tests for the human-readable change summary."""
+
+ def test_empty_on_no_changes(self):
+ assert generate_tag_types.build_summary([], [], []) == []
+
+ def test_added(self):
+ result = generate_tag_types.build_summary([1, 2], [], [])
+ assert len(result) == 1
+ assert "Added: 2" in result[0]
+
+ def test_truncated(self):
+ result = generate_tag_types.build_summary(list(range(10)), [], [])
+ assert "..." in result[0]
+
+
+# ---------------------------------------------------------------------------
+# Tests for generate_tag_types – set_github_output
+# ---------------------------------------------------------------------------
+
+class TestSetGithubOutput:
+ """Tests for writing GitHub Actions outputs."""
+
+ def test_writes_changed(self, tmp_path):
+ output_file = tmp_path / "output.txt"
+ output_file.write_text("")
+ with patch.dict(os.environ, {"GITHUB_OUTPUT": str(output_file)}):
+ generate_tag_types.set_github_output(True, ["Added: 1 types (5)"])
+ content = output_file.read_text()
+ assert "changed=true" in content
+ assert "summary=" in content
+
+ def test_no_op_without_env(self, tmp_path):
+ """Should not crash when GITHUB_OUTPUT is not set."""
+ with patch.dict(os.environ, {}, clear=True):
+ generate_tag_types.set_github_output(False, []) # should not raise
+
+
+# ---------------------------------------------------------------------------
+# Tests for generate_tag_types – full main() integration
+# ---------------------------------------------------------------------------
+
+class TestMainIntegration:
+ """Integration tests for the full generate_tag_types.main() flow."""
+
+ def test_no_change_run(self, tag_types_file, new_types_json, tmp_path):
+ """When data matches, output changed=false."""
+ output_file = tmp_path / "output.txt"
+ output_file.write_text("")
+ with patch.object(generate_tag_types, "TAG_TYPES_PATH", str(tag_types_file)), \
+ patch.dict(os.environ, {"GITHUB_OUTPUT": str(output_file)}), \
+ patch("sys.argv", ["prog", str(new_types_json)]):
+ generate_tag_types.main()
+ assert "changed=false" in output_file.read_text()
+
+ def test_added_type_run(self, tag_types_file, tmp_path):
+ """When a new type is added, output changed=true and file is updated."""
+ data = {
+ 0: {"version": 4, "name": 'M2 1.54"', "width": 152, "height": 152},
+ 1: {"version": 5, "name": 'M2 2.9"', "width": 296, "height": 128},
+ 240: {"version": 2, "name": "SLT\u2010EM007 Segmented", "width": 0, "height": 0},
+ 250: {"version": 1, "name": "ConfigMode", "width": 0, "height": 0},
+ 999: {"version": 1, "name": "Brand New", "width": 100, "height": 200},
+ }
+ json_file = tmp_path / "new.json"
+ json_file.write_text(json.dumps(data, indent=2))
+
+ output_file = tmp_path / "output.txt"
+ output_file.write_text("")
+ with patch.object(generate_tag_types, "TAG_TYPES_PATH", str(tag_types_file)), \
+ patch.dict(os.environ, {"GITHUB_OUTPUT": str(output_file)}), \
+ patch("sys.argv", ["prog", str(json_file)]):
+ generate_tag_types.main()
+ assert "changed=true" in output_file.read_text()
+ updated = tag_types_file.read_text()
+ assert "999:" in updated
+ assert "Brand New" in updated
+
+
+# ---------------------------------------------------------------------------
+# Tests for fetch_tag_types
+# ---------------------------------------------------------------------------
+
+class TestFetchTagTypes:
+ """Tests for the fetch_tag_types module."""
+
+ def test_fetch_file_list(self):
+ """fetch_file_list should parse JSON filenames from HTML."""
+ fake_html = '00.json 0A.json other.txt'
+ mock_response = MagicMock()
+ mock_response.read.return_value = fake_html.encode("utf-8")
+ mock_response.__enter__ = MagicMock(return_value=mock_response)
+ mock_response.__exit__ = MagicMock(return_value=False)
+
+ with patch("urllib.request.urlopen", return_value=mock_response):
+ result = fetch_tag_types.fetch_file_list()
+ assert result == ["00.json", "0A.json"]
+
+ def test_fetch_tag_types_parses_hex_ids(self):
+ """Filenames should be converted from hex to decimal type IDs."""
+ fake_json = json.dumps({
+ "version": 1, "name": "Test", "width": 100, "height": 50
+ }).encode("utf-8")
+
+ mock_response = MagicMock()
+ mock_response.read.return_value = fake_json
+ mock_response.__enter__ = MagicMock(return_value=mock_response)
+ mock_response.__exit__ = MagicMock(return_value=False)
+
+ with patch("urllib.request.urlopen", return_value=mock_response):
+ result = fetch_tag_types.fetch_tag_types(["0A.json"])
+ # 0x0A = 10
+ assert 10 in result
+ assert result[10]["name"] == "Test"
+
+ def test_fetch_tag_types_handles_errors(self):
+ """Errors fetching individual files should not crash the whole run."""
+ with patch("urllib.request.urlopen", side_effect=Exception("Network error")):
+ result = fetch_tag_types.fetch_tag_types(["00.json"])
+ assert result == {}
+
+ def test_main_writes_json(self, tmp_path):
+ """main() should write fetched data to the output JSON file."""
+ output = tmp_path / "out.json"
+
+ with patch.object(fetch_tag_types, "fetch_file_list", return_value=["01.json"]), \
+ patch.object(fetch_tag_types, "fetch_tag_types", return_value={
+ 1: {"version": 1, "name": "X", "width": 10, "height": 10}
+ }), \
+ patch("sys.argv", ["prog", str(output)]):
+ fetch_tag_types.main()
+
+ data = json.loads(output.read_text())
+ assert "1" in data # JSON keys are strings
+ assert data["1"]["name"] == "X"