fritzkit 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (328) hide show
  1. package/.claude-plugin/marketplace.json +12 -0
  2. package/.claude-plugin/plugin.json +13 -0
  3. package/CHANGELOG.md +8 -0
  4. package/LICENSE +21 -0
  5. package/README.md +429 -2
  6. package/dist/cli/commands/doctor.d.ts +4 -0
  7. package/dist/cli/commands/doctor.js +7 -0
  8. package/dist/cli/commands/doctor.js.map +1 -0
  9. package/dist/cli/commands/export.d.ts +17 -0
  10. package/dist/cli/commands/export.js +81 -0
  11. package/dist/cli/commands/export.js.map +1 -0
  12. package/dist/cli/commands/kicad.d.ts +10 -0
  13. package/dist/cli/commands/kicad.js +22 -0
  14. package/dist/cli/commands/kicad.js.map +1 -0
  15. package/dist/cli/commands/parts.d.ts +22 -0
  16. package/dist/cli/commands/parts.js +46 -0
  17. package/dist/cli/commands/parts.js.map +1 -0
  18. package/dist/cli/commands/prune.d.ts +14 -0
  19. package/dist/cli/commands/prune.js +67 -0
  20. package/dist/cli/commands/prune.js.map +1 -0
  21. package/dist/cli/commands/render.d.ts +24 -0
  22. package/dist/cli/commands/render.js +62 -0
  23. package/dist/cli/commands/render.js.map +1 -0
  24. package/dist/cli/commands/schema.d.ts +10 -0
  25. package/dist/cli/commands/schema.js +35 -0
  26. package/dist/cli/commands/schema.js.map +1 -0
  27. package/dist/cli/commands/ui.d.ts +14 -0
  28. package/dist/cli/commands/ui.js +67 -0
  29. package/dist/cli/commands/ui.js.map +1 -0
  30. package/dist/cli/commands/validate.d.ts +24 -0
  31. package/dist/cli/commands/validate.js +91 -0
  32. package/dist/cli/commands/validate.js.map +1 -0
  33. package/dist/cli/context.d.ts +81 -0
  34. package/dist/cli/context.js +139 -0
  35. package/dist/cli/context.js.map +1 -0
  36. package/dist/cli/main.d.ts +2 -0
  37. package/dist/cli/main.js +8 -0
  38. package/dist/cli/main.js.map +1 -0
  39. package/dist/cli/program.d.ts +21 -0
  40. package/dist/cli/program.js +158 -0
  41. package/dist/cli/program.js.map +1 -0
  42. package/dist/cli/raster.d.ts +4 -0
  43. package/dist/cli/raster.js +9 -0
  44. package/dist/cli/raster.js.map +1 -0
  45. package/dist/core/compile/breadboard-util.d.ts +12 -0
  46. package/dist/core/compile/breadboard-util.js +107 -0
  47. package/dist/core/compile/breadboard-util.js.map +1 -0
  48. package/dist/core/compile/breadboard.d.ts +60 -0
  49. package/dist/core/compile/breadboard.js +513 -0
  50. package/dist/core/compile/breadboard.js.map +1 -0
  51. package/dist/core/compile/fzp.d.ts +12 -0
  52. package/dist/core/compile/fzp.js +183 -0
  53. package/dist/core/compile/fzp.js.map +1 -0
  54. package/dist/core/compile/icon.d.ts +17 -0
  55. package/dist/core/compile/icon.js +27 -0
  56. package/dist/core/compile/icon.js.map +1 -0
  57. package/dist/core/compile/index.d.ts +4 -0
  58. package/dist/core/compile/index.js +62 -0
  59. package/dist/core/compile/index.js.map +1 -0
  60. package/dist/core/compile/pcb.d.ts +39 -0
  61. package/dist/core/compile/pcb.js +164 -0
  62. package/dist/core/compile/pcb.js.map +1 -0
  63. package/dist/core/compile/schematic.d.ts +69 -0
  64. package/dist/core/compile/schematic.js +240 -0
  65. package/dist/core/compile/schematic.js.map +1 -0
  66. package/dist/core/compile/style.d.ts +52 -0
  67. package/dist/core/compile/style.js +72 -0
  68. package/dist/core/compile/style.js.map +1 -0
  69. package/dist/core/compile/types.d.ts +50 -0
  70. package/dist/core/compile/types.js +15 -0
  71. package/dist/core/compile/types.js.map +1 -0
  72. package/dist/core/export/bin.d.ts +61 -0
  73. package/dist/core/export/bin.js +221 -0
  74. package/dist/core/export/bin.js.map +1 -0
  75. package/dist/core/export/fzpz.d.ts +37 -0
  76. package/dist/core/export/fzpz.js +121 -0
  77. package/dist/core/export/fzpz.js.map +1 -0
  78. package/dist/core/export/index.d.ts +5 -0
  79. package/dist/core/export/index.js +6 -0
  80. package/dist/core/export/index.js.map +1 -0
  81. package/dist/core/export/install.d.ts +77 -0
  82. package/dist/core/export/install.js +183 -0
  83. package/dist/core/export/install.js.map +1 -0
  84. package/dist/core/export/prune.d.ts +72 -0
  85. package/dist/core/export/prune.js +165 -0
  86. package/dist/core/export/prune.js.map +1 -0
  87. package/dist/core/fritzing.d.ts +22 -0
  88. package/dist/core/fritzing.js +77 -0
  89. package/dist/core/fritzing.js.map +1 -0
  90. package/dist/core/identity.d.ts +29 -0
  91. package/dist/core/identity.js +70 -0
  92. package/dist/core/identity.js.map +1 -0
  93. package/dist/core/image/bbox.d.ts +27 -0
  94. package/dist/core/image/bbox.js +24 -0
  95. package/dist/core/image/bbox.js.map +1 -0
  96. package/dist/core/image/index.d.ts +8 -0
  97. package/dist/core/image/index.js +9 -0
  98. package/dist/core/image/index.js.map +1 -0
  99. package/dist/core/image/key.d.ts +72 -0
  100. package/dist/core/image/key.js +317 -0
  101. package/dist/core/image/key.js.map +1 -0
  102. package/dist/core/image/prepare.d.ts +31 -0
  103. package/dist/core/image/prepare.js +98 -0
  104. package/dist/core/image/prepare.js.map +1 -0
  105. package/dist/core/image/remover.d.ts +17 -0
  106. package/dist/core/image/remover.js +51 -0
  107. package/dist/core/image/remover.js.map +1 -0
  108. package/dist/core/index.d.ts +16 -0
  109. package/dist/core/index.js +17 -0
  110. package/dist/core/index.js.map +1 -0
  111. package/dist/core/kicad/footprint.d.ts +104 -0
  112. package/dist/core/kicad/footprint.js +317 -0
  113. package/dist/core/kicad/footprint.js.map +1 -0
  114. package/dist/core/kicad/index.d.ts +25 -0
  115. package/dist/core/kicad/index.js +197 -0
  116. package/dist/core/kicad/index.js.map +1 -0
  117. package/dist/core/kicad/sexpr.d.ts +35 -0
  118. package/dist/core/kicad/sexpr.js +163 -0
  119. package/dist/core/kicad/sexpr.js.map +1 -0
  120. package/dist/core/resolve.d.ts +113 -0
  121. package/dist/core/resolve.js +301 -0
  122. package/dist/core/resolve.js.map +1 -0
  123. package/dist/core/schema/part.d.ts +995 -0
  124. package/dist/core/schema/part.js +278 -0
  125. package/dist/core/schema/part.js.map +1 -0
  126. package/dist/core/schema/part.schema.json +1122 -0
  127. package/dist/core/svg.d.ts +20 -0
  128. package/dist/core/svg.js +56 -0
  129. package/dist/core/svg.js.map +1 -0
  130. package/dist/core/units.d.ts +28 -0
  131. package/dist/core/units.js +42 -0
  132. package/dist/core/units.js.map +1 -0
  133. package/dist/core/validate/checker.d.ts +46 -0
  134. package/dist/core/validate/checker.js +142 -0
  135. package/dist/core/validate/checker.js.map +1 -0
  136. package/dist/core/validate/doctor.d.ts +25 -0
  137. package/dist/core/validate/doctor.js +156 -0
  138. package/dist/core/validate/doctor.js.map +1 -0
  139. package/dist/core/validate/explain.d.ts +10 -0
  140. package/dist/core/validate/explain.js +135 -0
  141. package/dist/core/validate/explain.js.map +1 -0
  142. package/dist/core/validate/index.d.ts +21 -0
  143. package/dist/core/validate/index.js +96 -0
  144. package/dist/core/validate/index.js.map +1 -0
  145. package/dist/core/validate/structural.d.ts +74 -0
  146. package/dist/core/validate/structural.js +664 -0
  147. package/dist/core/validate/structural.js.map +1 -0
  148. package/dist/core/workspace.d.ts +69 -0
  149. package/dist/core/workspace.js +213 -0
  150. package/dist/core/workspace.js.map +1 -0
  151. package/dist/mcp/jsonpatch.d.ts +30 -0
  152. package/dist/mcp/jsonpatch.js +145 -0
  153. package/dist/mcp/jsonpatch.js.map +1 -0
  154. package/dist/mcp/main.d.ts +2 -0
  155. package/dist/mcp/main.js +5 -0
  156. package/dist/mcp/main.js.map +1 -0
  157. package/dist/mcp/server.d.ts +28 -0
  158. package/dist/mcp/server.js +310 -0
  159. package/dist/mcp/server.js.map +1 -0
  160. package/dist/server/api.d.ts +72 -0
  161. package/dist/server/api.js +507 -0
  162. package/dist/server/api.js.map +1 -0
  163. package/dist/server/events.d.ts +28 -0
  164. package/dist/server/events.js +134 -0
  165. package/dist/server/events.js.map +1 -0
  166. package/dist/server/http.d.ts +36 -0
  167. package/dist/server/http.js +127 -0
  168. package/dist/server/http.js.map +1 -0
  169. package/dist/server/index.d.ts +65 -0
  170. package/dist/server/index.js +193 -0
  171. package/dist/server/index.js.map +1 -0
  172. package/dist/ui/assets/index-DLU8opyI.js +63 -0
  173. package/dist/ui/assets/index-GhUA-T3b.css +1 -0
  174. package/dist/ui/index.html +14 -0
  175. package/package.json +89 -4
  176. package/skills/fritzkit/SKILL.md +690 -0
  177. package/skills/fritzkit/reference/examples.md +275 -0
  178. package/skills/fritzkit/reference/part-json.md +333 -0
  179. package/skills/fritzkit/scripts/measure.py +197 -0
  180. package/tools/fzp-checker/.python-version +1 -0
  181. package/tools/fzp-checker/README.md +45 -0
  182. package/tools/fzp-checker/check_fzp.py +48 -0
  183. package/tools/fzp-checker/pyproject.toml +26 -0
  184. package/tools/fzp-checker/uv.lock +266 -0
  185. package/tools/fzp-checker/vendor/LICENSE.txt +8 -0
  186. package/tools/fzp-checker/vendor/UPSTREAM.md +25 -0
  187. package/tools/fzp-checker/vendor/fzp_checker.py +14 -0
  188. package/tools/fzp-checker/vendor/scripts/checks/__init__.py +26 -0
  189. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/__init__.cpython-313.pyc +0 -0
  190. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/check_missing_leg_ids.cpython-313.pyc +0 -0
  191. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/connector_numbering.cpython-313.pyc +0 -0
  192. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/fzp_checker_runner.cpython-313.pyc +0 -0
  193. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/fzp_checkers.cpython-313.pyc +0 -0
  194. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/fzp_svg_checkers.cpython-313.pyc +0 -0
  195. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/fzp_utils.cpython-313.pyc +0 -0
  196. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/svg_checkers.cpython-313.pyc +0 -0
  197. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/svg_utils.cpython-313.pyc +0 -0
  198. package/tools/fzp-checker/vendor/scripts/checks/check_missing_leg_ids.py +44 -0
  199. package/tools/fzp-checker/vendor/scripts/checks/checkascii.py +27 -0
  200. package/tools/fzp-checker/vendor/scripts/checks/checkcase.py +156 -0
  201. package/tools/fzp-checker/vendor/scripts/checks/checkcopies.py +104 -0
  202. package/tools/fzp-checker/vendor/scripts/checks/connector_numbering.py +79 -0
  203. package/tools/fzp-checker/vendor/scripts/checks/connectors_misnumbered.py +228 -0
  204. package/tools/fzp-checker/vendor/scripts/checks/explain_errors.md +95 -0
  205. package/tools/fzp-checker/vendor/scripts/checks/fzp_checker_runner.py +717 -0
  206. package/tools/fzp-checker/vendor/scripts/checks/fzp_checkers.py +1069 -0
  207. package/tools/fzp-checker/vendor/scripts/checks/fzp_svg_checkers.py +62 -0
  208. package/tools/fzp-checker/vendor/scripts/checks/fzp_utils.py +222 -0
  209. package/tools/fzp-checker/vendor/scripts/checks/inspector_fc51.png +0 -0
  210. package/tools/fzp-checker/vendor/scripts/checks/invisibleconnectors.py +118 -0
  211. package/tools/fzp-checker/vendor/scripts/checks/nolayeridcheck.txt +758 -0
  212. package/tools/fzp-checker/vendor/scripts/checks/requirements.txt +3 -0
  213. package/tools/fzp-checker/vendor/scripts/checks/svg_checkers.py +953 -0
  214. package/tools/fzp-checker/vendor/scripts/checks/svg_utils.py +96 -0
  215. package/tools/fzp-checker/vendor/scripts/checks/test_checkers.py +839 -0
  216. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/author_missing.fzp.test +3 -0
  217. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/author_present.fzp.test +4 -0
  218. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/buses_missing_id.fzp.test +8 -0
  219. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/buses_missing_node_members.fzp.test +7 -0
  220. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/buses_present_valid.fzp.test +8 -0
  221. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/connector_layers_missing_attributes.fzp.test +16 -0
  222. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/connector_layers_present_valid.fzp.test +14 -0
  223. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/connector_numbering_invalid.fzp.test +70 -0
  224. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/connector_numbering_valid.fzp.test +70 -0
  225. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/connector_refs_valid.fzp.test +53 -0
  226. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_invalid_mixed.fzp.test +11 -0
  227. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_invalid_silkscreen_nested.fzp.test +11 -0
  228. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_invalid_style_attr.fzp.test +11 -0
  229. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_invalid_white_stroke.fzp.test +11 -0
  230. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_invalid_wrong_color.fzp.test +11 -0
  231. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_valid_inherited.fzp.test +11 -0
  232. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_valid_legacy.fzp.test +12 -0
  233. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_valid_nested_copper.fzp.test +12 -0
  234. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_valid_none.fzp.test +11 -0
  235. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_valid_standard.fzp.test +12 -0
  236. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_valid_uppercase.fzp.test +11 -0
  237. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/css_connector.fzp.test +100 -0
  238. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/description_missing.fzp.test +3 -0
  239. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/description_present.fzp.test +4 -0
  240. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/duplicate_ids.fzp.test +27 -0
  241. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/family_property_missing.fzp.test +6 -0
  242. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/family_property_present_valid.fzp.test +6 -0
  243. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/font_size.fzp.test +46 -0
  244. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/fritzing_version_invalid_format.fzp.test +4 -0
  245. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/fritzing_version_missing.fzp.test +4 -0
  246. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/fritzing_version_present_valid.fzp.test +4 -0
  247. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/gorn_absent.fzp.test +15 -0
  248. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/gorn_present.fzp.test +15 -0
  249. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/hybrid_connectors.fzp.test +232 -0
  250. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/invalid_xml.fzp.test +12 -0
  251. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/layer_ids_match.fzp.test +29 -0
  252. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/layer_ids_mismatch.fzp.test +30 -0
  253. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/layer_nesting_invalid.fzp.test +11 -0
  254. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/layer_nesting_valid.fzp.test +11 -0
  255. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/matrix_transform.fzp.test +8 -0
  256. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/missing_connector_refs.fzp.test +63 -0
  257. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/missing_leg_ids.fzp.test +34 -0
  258. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/module_id_missing.fzp.test +4 -0
  259. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/module_id_non_alnum.fzp.test +4 -0
  260. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/module_id_present.fzp.test +4 -0
  261. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/module_id_special_chars_absent.fzp.test +4 -0
  262. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/module_id_special_chars_present.fzp.test +4 -0
  263. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/module_id_too_short.fzp.test +4 -0
  264. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/no_layer_invalid.fzp.test +19 -0
  265. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/no_layer_namedview.fzp.test +19 -0
  266. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/no_layer_valid.fzp.test +19 -0
  267. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/pcb_only.fzp.test +48 -0
  268. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/property_fields_missing_name.fzp.test +6 -0
  269. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/property_fields_missing_value.fzp.test +6 -0
  270. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/property_fields_present.fzp.test +6 -0
  271. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/required_tags_missing.fzp.test +3 -0
  272. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/required_tags_present.fzp.test +11 -0
  273. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/stroke_test.fzp.test +60 -0
  274. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/template_svgs.fzp.test +35 -0
  275. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/title_missing.fzp.test +3 -0
  276. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/title_present.fzp.test +4 -0
  277. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/unique_ids.fzp.test +27 -0
  278. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/unique_property_names_duplicates.fzp.test +7 -0
  279. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/unique_property_names_unique.fzp.test +7 -0
  280. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/valid_xml.fzp.test +249 -0
  281. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/version_invalid_format.fzp.test +4 -0
  282. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/version_missing.fzp.test +3 -0
  283. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/version_present_valid.fzp.test +4 -0
  284. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/views_missing.fzp.test +3 -0
  285. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/views_present.fzp.test +8 -0
  286. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/connector_refs_valid_breadboard.svg +7 -0
  287. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/duplicate_ids_breadboard.svg +13 -0
  288. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/gorn_absent_breadboard.svg +7 -0
  289. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/gorn_present_breadboard.svg +7 -0
  290. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/hybrid_connectors_breadboard.svg +102 -0
  291. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/layer_nesting_invalid.svg +13 -0
  292. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/layer_nesting_valid.svg +13 -0
  293. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/matrix_transform.svg +11 -0
  294. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/missing_connector_refs_breadboard.svg +8 -0
  295. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/missing_leg_ids_breadboard.svg +9 -0
  296. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/test_layer_ids_match_breadboard.svg +9 -0
  297. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/test_layer_ids_mismatch_breadboard.svg +9 -0
  298. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/unique_ids_breadboard.svg +13 -0
  299. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/icon/hybrid_connectors_icon.svg +78 -0
  300. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/icon/no_layer_invalid.svg +4 -0
  301. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/icon/no_layer_namedview.svg +9 -0
  302. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/icon/no_layer_valid.svg +7 -0
  303. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/icon/plain_pcb.svg +10 -0
  304. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/connector_refs_valid_pcb.svg +10 -0
  305. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_invalid_mixed.svg +8 -0
  306. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_invalid_silkscreen_nested.svg +10 -0
  307. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_invalid_style_attr.svg +6 -0
  308. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_invalid_white_stroke.svg +6 -0
  309. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_invalid_wrong_color.svg +6 -0
  310. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_valid_inherited.svg +7 -0
  311. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_valid_legacy.svg +10 -0
  312. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_valid_nested_copper.svg +9 -0
  313. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_valid_none.svg +7 -0
  314. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_valid_standard.svg +10 -0
  315. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_valid_uppercase.svg +7 -0
  316. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/css_connector.svg +25 -0
  317. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/font_size.svg +54 -0
  318. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/hybrid_connectors_pcb.svg +35 -0
  319. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/missing_connector_refs_pcb.svg +14 -0
  320. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/pcb-arduino-shield.svg +25 -0
  321. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/stroke_test.svg +12 -0
  322. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/test_layer_ids_match_pcb.svg +12 -0
  323. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/test_layer_ids_mismatch_pcb.svg +12 -0
  324. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/schematic/connector_refs_valid_schematic.svg +7 -0
  325. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/schematic/hybrid_connectors_schematic.svg +59 -0
  326. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/schematic/missing_connector_refs_schematic.svg +8 -0
  327. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/schematic/test_layer_ids_match_schematic.svg +6 -0
  328. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/schematic/test_layer_ids_mismatch_schematic.svg +6 -0
@@ -0,0 +1,839 @@
1
+ import unittest
2
+ import os
3
+ import sys
4
+ from io import StringIO
5
+ from .fzp_checker_runner import FZPCheckerRunner, AVAILABLE_CHECKERS, SVG_AVAILABLE_CHECKERS
6
+ import tempfile
7
+ import shutil
8
+ from lxml import etree
9
+ from .svg_checkers import SVGIdsChecker
10
+
11
+
12
+ class TestCheckers(unittest.TestCase):
13
+ def setUp(self):
14
+ self.test_data_dir = 'test_data/core'
15
+ self.verbose = True
16
+
17
+ def test_valid_xml(self):
18
+ fzp_file = os.path.join(self.test_data_dir, 'valid_xml.fzp.test')
19
+ checker_runner = FZPCheckerRunner(fzp_file)
20
+
21
+ captured_output = StringIO()
22
+ sys.stdout = captured_output
23
+ checker_runner.check([], [])
24
+ sys.stdout = sys.__stdout__
25
+
26
+ # 4 missing SVG file errors (test data doesn't include the referenced SVGs)
27
+ self.assertEqual(checker_runner.total_errors, 4)
28
+ self.assertNotIn('Invalid XML', captured_output.getvalue())
29
+
30
+ def test_invalid_xml(self):
31
+ fzp_file = os.path.join(self.test_data_dir, 'invalid_xml.fzp.test')
32
+ checker_runner = FZPCheckerRunner(fzp_file)
33
+
34
+ checker_runner.check([], [])
35
+
36
+ self.assertEqual(checker_runner.total_errors, 1)
37
+ # Check that the error is recorded in the errors list
38
+ self.assertTrue(len(checker_runner.all_issues) > 0)
39
+ error_messages = [issue.message for issue in checker_runner.all_issues]
40
+ self.assertTrue(any('Invalid XML' in msg for msg in error_messages))
41
+
42
+ def run_checker(self, fzp_filename, fzp_checkers, svg_checkers, expected_errors, expected_message, expected_warnings=None):
43
+ fzp_file = os.path.join(self.test_data_dir, fzp_filename)
44
+ checker_runner = FZPCheckerRunner(fzp_file)
45
+
46
+ # Run specific FZP and SVG checkers for this test case
47
+ checker_runner.check(fzp_checkers, svg_checkers)
48
+
49
+ self.assertEqual(expected_errors, checker_runner.total_errors)
50
+ if expected_warnings is not None:
51
+ self.assertEqual(expected_warnings, checker_runner.total_warnings)
52
+
53
+ def test_pcb_only_part(self):
54
+ self.run_checker('pcb_only.fzp.test',
55
+ ['missing_tags','connector_terminal','connector_svg_ref'],
56
+ ['font_size','viewbox','ids'], 0, None)
57
+
58
+ def test_hybrid_connectors_part(self):
59
+ self.run_checker('hybrid_connectors.fzp.test',
60
+ ['missing_tags','connector_terminal','connector_svg_ref'],
61
+ ['font_size','viewbox','ids'], 0, None)
62
+ # The errors are now 0 after the invisibility check has been removed.
63
+
64
+ def test_css_connector_part(self):
65
+ self.run_checker('css_connector.fzp.test',
66
+ ['connector_terminal','connector_svg_ref'],
67
+ [], 0, None)
68
+ # The errors are now 0 after the invisibility check has been removed.
69
+
70
+ def test_font_size(self):
71
+ self.run_checker('font_size.fzp.test',
72
+ [], ['font_size'], 4, None)
73
+ # Expected errors:
74
+ # No font size found for element [
75
+ # Test No Font Size 1
76
+ # ]
77
+ # No font size found for element [ Test No Font Size 2 ]
78
+ # Invalid font size 5px unit in element: [
79
+ # Test px unit
80
+ # ]
81
+ # Invalid font size 2mm unit in element: [
82
+ # Test mm unit
83
+ # ]
84
+
85
+ def test_font_size_fix(self):
86
+ """Test that font-size unit suffixes are automatically removed"""
87
+ test_svg = 'test_data/svg/core/pcb/font_size.svg'
88
+
89
+ with tempfile.TemporaryDirectory() as temp_dir:
90
+ temp_svg = os.path.join(temp_dir, 'temp_font_size.svg')
91
+ shutil.copy(test_svg, temp_svg)
92
+
93
+ # Parse and check - should find unit errors
94
+ svg_doc = etree.parse(temp_svg)
95
+ from .svg_checkers import SVGFontSizeChecker
96
+ checker = SVGFontSizeChecker(svg_doc, ['silkscreen'])
97
+ errors, warnings = checker.check()
98
+ self.assertGreater(errors, 0, "Should initially have font-size errors")
99
+
100
+ # Apply fix
101
+ fix_results = checker.fix(temp_svg)
102
+ self.assertGreater(len(fix_results), 0, "Should have applied fixes")
103
+
104
+ # Verify the file no longer has unit suffixes
105
+ with open(temp_svg, 'r') as f:
106
+ content = f.read()
107
+ self.assertNotIn('font-size="5px"', content)
108
+ self.assertNotIn('font-size="2mm"', content)
109
+ self.assertIn('font-size="5"', content)
110
+ self.assertIn('font-size="2"', content)
111
+
112
+ # Re-check: only the 2 "missing font-size" errors should remain
113
+ svg_doc2 = etree.parse(temp_svg)
114
+ checker2 = SVGFontSizeChecker(svg_doc2, ['silkscreen'])
115
+ errors2, _ = checker2.check()
116
+ self.assertEqual(errors2, 2, "After fix, only missing font-size errors should remain")
117
+
118
+ # def test_missing_tags(self):
119
+ # self.run_checker('missing_tags.fzp.test', ['missing_tags'], [], 1, 'Missing required tag')
120
+ #
121
+ # def test_invalid_terminal(self):
122
+ # self.run_checker('invalid_terminal.fzp.test', ['connector_terminal'], [], 1, 'references missing terminal')
123
+ #
124
+ # def test_invisible_connector(self):
125
+ # self.run_checker('invisible_connector.fzp.test', ['connector_svg_ref'], [], 1, 'Invisible connector')
126
+
127
+ def test_stroke_checker(self):
128
+ self.run_checker('stroke_test.fzp.test',
129
+ ['pcb_connector_stroke'], [], 2, None)
130
+
131
+ def test_missing_connector_refs(self):
132
+ self.run_checker('missing_connector_refs.fzp.test',
133
+ ['missing_connector_refs'],
134
+ [], 4, None) # 4 errors: 2 copper0, 1 breadboard, 1 schematic
135
+
136
+ def test_connector_refs_valid(self):
137
+ self.run_checker('connector_refs_valid.fzp.test',
138
+ ['missing_connector_refs'],
139
+ [], 0, None) # No errors when all references are present
140
+
141
+ def test_missing_leg_ids(self):
142
+ self.run_checker('missing_leg_ids.fzp.test',
143
+ ['missing_leg_ids'],
144
+ [], 2, None) # 2 errors for missing leg refs
145
+
146
+ def test_fritzing_version_present_valid(self):
147
+ self.run_checker('fritzing_version_present_valid.fzp.test',
148
+ ['fritzing_version'],
149
+ [], 0, None, 0)
150
+
151
+ def test_fritzing_version_missing(self):
152
+ self.run_checker('fritzing_version_missing.fzp.test',
153
+ ['fritzing_version'],
154
+ [], 1, None, 0)
155
+
156
+ def test_fritzing_version_missing(self):
157
+ self.run_checker('fritzing_version_invalid_format.fzp.test',
158
+ ['fritzing_version'],
159
+ [], 1, None, 0)
160
+
161
+ def _make_fzp_tree(self, xml_string):
162
+ """Helper: parse XML string into an ElementTree (as FZPChecker expects)"""
163
+ root = etree.fromstring(xml_string)
164
+ return etree.ElementTree(root)
165
+
166
+ def test_fritzing_version_range_current(self):
167
+ """Test that version >= 1.0.4 passes without issues"""
168
+ from .fzp_checkers import FZPFritzingVersionRangeChecker
169
+ fzp_doc = self._make_fzp_tree('<module fritzingVersion="1.0.4" moduleId="test"/>')
170
+ checker = FZPFritzingVersionRangeChecker(fzp_doc)
171
+ errors, warnings = checker.check()
172
+ self.assertEqual(errors, 0)
173
+ self.assertEqual(warnings, 0)
174
+
175
+ def test_fritzing_version_range_outdated_conventions(self):
176
+ """Test that version < 1.0.4 but >= 0.9.4 warns about outdated conventions"""
177
+ from .fzp_checkers import FZPFritzingVersionRangeChecker
178
+ fzp_doc = self._make_fzp_tree('<module fritzingVersion="1.0.3" moduleId="test"/>')
179
+ checker = FZPFritzingVersionRangeChecker(fzp_doc)
180
+ errors, warnings = checker.check()
181
+ self.assertEqual(errors, 0)
182
+ self.assertEqual(warnings, 1)
183
+ self.assertIn("outdated conventions", checker.issues[0].message)
184
+
185
+ def test_fritzing_version_range_very_old(self):
186
+ """Test that version < 0.8 produces error + ten years old warning"""
187
+ from .fzp_checkers import FZPFritzingVersionRangeChecker
188
+ fzp_doc = self._make_fzp_tree('<module fritzingVersion="0.5.2b.02.18.4756" moduleId="test"/>')
189
+ checker = FZPFritzingVersionRangeChecker(fzp_doc)
190
+ errors, warnings = checker.check()
191
+ self.assertEqual(errors, 1)
192
+ self.assertEqual(warnings, 1)
193
+ error_msgs = [i.message for i in checker.issues if i.severity == 'error']
194
+ warning_msgs = [i.message for i in checker.issues if i.severity == 'warning']
195
+ self.assertIn("below 0.8", error_msgs[0])
196
+ self.assertIn("ten years old", warning_msgs[0])
197
+
198
+ def test_fritzing_version_range_at_threshold(self):
199
+ """Test that version exactly 0.9.4 gets the outdated conventions warning"""
200
+ from .fzp_checkers import FZPFritzingVersionRangeChecker
201
+ fzp_doc = self._make_fzp_tree('<module fritzingVersion="0.9.4" moduleId="test"/>')
202
+ checker = FZPFritzingVersionRangeChecker(fzp_doc)
203
+ errors, warnings = checker.check()
204
+ self.assertEqual(errors, 0)
205
+ self.assertEqual(warnings, 1)
206
+ self.assertIn("outdated conventions", checker.issues[0].message)
207
+
208
+ def test_module_id_present(self):
209
+ self.run_checker('module_id_present.fzp.test',
210
+ ['module_id'],
211
+ [], 0, None, 0)
212
+
213
+ def test_module_id_missing(self):
214
+ self.run_checker('module_id_missing.fzp.test',
215
+ ['module_id'],
216
+ [], 1, None, 0)
217
+
218
+ def test_module_id_unsafe_filename_chars(self):
219
+ self.run_checker('module_id_special_chars_present.fzp.test',
220
+ ['module_id_special_chars'],
221
+ [], 1, None, 0) # 1 error for '*' (unsafe for filenames)
222
+
223
+ def test_module_id_clean(self):
224
+ self.run_checker('module_id_special_chars_absent.fzp.test',
225
+ ['module_id_special_chars'],
226
+ [], 0, None, 0)
227
+
228
+ def test_module_id_non_alnum_warns(self):
229
+ self.run_checker('module_id_non_alnum.fzp.test',
230
+ ['module_id_special_chars'],
231
+ [], 0, None, 1) # 1 warning for space
232
+
233
+ def test_module_id_too_short(self):
234
+ self.run_checker('module_id_too_short.fzp.test',
235
+ ['module_id_special_chars'],
236
+ [], 1, None, 0) # 1 error for too short (<8 chars)
237
+
238
+ def test_module_id_special_chars_fix(self):
239
+ """Test that the fixer replaces unsafe and non-alnum chars with underscores."""
240
+ import tempfile
241
+ import shutil
242
+ src = os.path.join(self.test_data_dir, 'module_id_special_chars_present.fzp.test')
243
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.fzp', delete=False) as tmp:
244
+ tmp_path = tmp.name
245
+ shutil.copy2(src, tmp_path)
246
+ try:
247
+ runner = FZPCheckerRunner(tmp_path)
248
+ runner.check(['module_id_special_chars'], [], fix=True)
249
+ with open(tmp_path, 'r', encoding='UTF-8') as f:
250
+ content = f.read()
251
+ self.assertIn('moduleId="SparkFun-DigitalIC-74_08-SE"', content)
252
+ finally:
253
+ os.unlink(tmp_path)
254
+
255
+ def test_version_present_valid(self):
256
+ self.run_checker('version_present_valid.fzp.test',
257
+ ['version'],
258
+ [], 0, None, 0)
259
+
260
+ def test_version_missing(self):
261
+ self.run_checker('version_missing.fzp.test',
262
+ ['version'],
263
+ [], 0, None, 1)
264
+
265
+ def test_version_invalid_format(self):
266
+ self.run_checker('version_invalid_format.fzp.test',
267
+ ['version'],
268
+ [], 0, None, 1)
269
+
270
+ def test_title_present(self):
271
+ self.run_checker('title_present.fzp.test',
272
+ ['title'],
273
+ [], 0, None, 0)
274
+
275
+ def test_title_missing(self):
276
+ self.run_checker('title_missing.fzp.test',
277
+ ['title'],
278
+ [], 1, None, 0)
279
+
280
+ def test_description_present(self):
281
+ self.run_checker('description_present.fzp.test',
282
+ ['description'],
283
+ [], 0, None, 0)
284
+
285
+ def test_description_missing(self):
286
+ self.run_checker('description_missing.fzp.test',
287
+ ['description'],
288
+ [], 0, None, 1)
289
+
290
+ def test_author_present(self):
291
+ self.run_checker('author_present.fzp.test',
292
+ ['author'],
293
+ [], 0, None, 0)
294
+
295
+ def test_author_missing(self):
296
+ self.run_checker('author_missing.fzp.test',
297
+ ['author'],
298
+ [], 0, None, 1)
299
+
300
+ def test_required_tags_present(self):
301
+ self.run_checker('required_tags_present.fzp.test',
302
+ ['required_tags'],
303
+ [], 4, None, 0)
304
+
305
+ def test_required_tags_missing(self):
306
+ self.run_checker('required_tags_missing.fzp.test',
307
+ ['required_tags'],
308
+ [], 6, None, 0)
309
+
310
+ def test_family_property_present_and_valid(self):
311
+ self.run_checker('family_property_present_valid.fzp.test',
312
+ ['family_property'],
313
+ [], 0, None, 0)
314
+
315
+ def test_family_property_missing(self):
316
+ self.run_checker('family_property_missing.fzp.test',
317
+ ['family_property'],
318
+ [], 1, None, 0)
319
+
320
+ def test_unique_property_names_unique(self):
321
+ self.run_checker('unique_property_names_unique.fzp.test',
322
+ ['unique_property_names'],
323
+ [], 0, None, 0)
324
+
325
+ def test_unique_property_names_duplicates(self):
326
+ self.run_checker('unique_property_names_duplicates.fzp.test',
327
+ ['unique_property_names'],
328
+ [], 1, None, 0)
329
+
330
+ def test_property_fields_present(self):
331
+ self.run_checker('property_fields_present.fzp.test',
332
+ ['property_fields'],
333
+ [], 0, None, 0)
334
+
335
+ def test_property_fields_missing_name(self):
336
+ self.run_checker('property_fields_missing_name.fzp.test',
337
+ ['property_fields'],
338
+ [], 1, None, 0)
339
+
340
+ def test_property_fields_missing_value(self):
341
+ self.run_checker('property_fields_missing_value.fzp.test',
342
+ ['property_fields'],
343
+ [], 1, None, 0)
344
+
345
+ def test_views_present(self):
346
+ self.run_checker('views_present.fzp.test',
347
+ ['views'],
348
+ [], 3, None, 0)
349
+
350
+ def test_views_missing(self):
351
+ self.run_checker('views_missing.fzp.test',
352
+ ['views'],
353
+ [], 1, None, 0)
354
+
355
+ def test_buses_present_valid(self):
356
+ self.run_checker('buses_present_valid.fzp.test',
357
+ ['buses'],
358
+ [], 0, None, 0)
359
+
360
+ def test_buses_missing_id(self):
361
+ self.run_checker('buses_missing_id.fzp.test',
362
+ ['buses'],
363
+ [], 1, None, 0)
364
+
365
+ def test_buses_missing_node_members(self):
366
+ self.run_checker('buses_missing_node_members.fzp.test',
367
+ ['buses'],
368
+ [], 1, None, 0)
369
+
370
+ def test_connector_layers_present_valid(self):
371
+ self.run_checker('connector_layers_present_valid.fzp.test',
372
+ ['connector_layers'],
373
+ [], 0, None, 0)
374
+
375
+ def test_connector_layers_missing_attributes(self):
376
+ self.run_checker('connector_layers_missing_attributes.fzp.test',
377
+ ['connector_layers'],
378
+ [], 4, None, 0)
379
+
380
+
381
+ def test_layer_ids_match(self):
382
+ self.run_checker('layer_ids_match.fzp.test',
383
+ ['layer_ids'],
384
+ [], 0, None)
385
+
386
+ def test_layer_ids_mismatch(self):
387
+ self.run_checker('layer_ids_mismatch.fzp.test',
388
+ ['layer_ids'],
389
+ [], 3, None)
390
+
391
+ def test_matrix_transform(self):
392
+ self.run_checker('matrix_transform.fzp.test',
393
+ [],
394
+ ['matrix'],
395
+ 5,
396
+ None)
397
+
398
+ def test_layer_nesting_valid(self):
399
+ self.run_checker('layer_nesting_valid.fzp.test',
400
+ [],
401
+ ['layer_nesting'],
402
+ 0,
403
+ None)
404
+
405
+ def test_layer_nesting_invalid(self):
406
+ self.run_checker('layer_nesting_invalid.fzp.test',
407
+ [],
408
+ ['layer_nesting'],
409
+ 2,
410
+ None)
411
+
412
+ def test_gorn_present(self):
413
+ """Test that gorn attributes are detected in SVG files"""
414
+ self.run_checker('gorn_present.fzp.test',
415
+ [],
416
+ ['svg-gorn'],
417
+ 3, # Should find 3 gorn attributes
418
+ None)
419
+
420
+ def test_gorn_absent(self):
421
+ """Test that files without gorn attributes pass the check"""
422
+ self.run_checker('gorn_absent.fzp.test',
423
+ [],
424
+ ['svg-gorn'],
425
+ 0, # Should find no gorn attributes
426
+ None)
427
+
428
+ def test_gorn_fix(self):
429
+ """Test that gorn attributes can be automatically removed"""
430
+ import tempfile
431
+ import shutil
432
+
433
+ # Create temporary copies of test files
434
+ test_fzp = os.path.join(self.test_data_dir, 'gorn_present.fzp.test')
435
+ test_svg = 'test_data/svg/core/breadboard/gorn_present_breadboard.svg'
436
+
437
+ with tempfile.TemporaryDirectory() as temp_dir:
438
+ # Copy test files to temp directory
439
+ temp_fzp = os.path.join(temp_dir, 'temp_gorn.fzp')
440
+ temp_svg = os.path.join(temp_dir, 'temp_gorn.svg')
441
+
442
+ shutil.copy(test_fzp, temp_fzp)
443
+ shutil.copy(test_svg, temp_svg)
444
+
445
+ # Update FZP to reference temp SVG
446
+ with open(temp_fzp, 'r') as f:
447
+ content = f.read()
448
+ content = content.replace('gorn_present_breadboard.svg', 'temp_gorn.svg')
449
+ with open(temp_fzp, 'w') as f:
450
+ f.write(content)
451
+
452
+ # Test initial state - should have gorn attributes
453
+ checker_runner = FZPCheckerRunner(temp_fzp)
454
+ checker_runner.check([], ['svg-gorn'], fix=False)
455
+ self.assertEqual(checker_runner.total_errors, 3, "Should initially have 3 gorn errors")
456
+
457
+ # Apply fix
458
+ checker_runner = FZPCheckerRunner(temp_fzp)
459
+ checker_runner.check([], ['svg-gorn'], fix=True)
460
+
461
+ # Check that fix was successful
462
+ checker_runner = FZPCheckerRunner(temp_fzp)
463
+ checker_runner.check([], ['svg-gorn'], fix=False)
464
+ self.assertEqual(checker_runner.total_errors, 0, "Should have no gorn errors after fix")
465
+
466
+ def test_unique_ids_valid(self):
467
+ """Test that files with unique IDs pass the check"""
468
+ self.run_checker('unique_ids.fzp.test',
469
+ [],
470
+ ['ids'],
471
+ 0, # Should find no duplicate IDs
472
+ None)
473
+
474
+ def test_duplicate_ids_invalid(self):
475
+ """Test that duplicate IDs are detected"""
476
+ self.run_checker('duplicate_ids.fzp.test',
477
+ [],
478
+ ['ids'],
479
+ 5, # Should find 5 duplicate ID errors (5 elements with id="label")
480
+ None)
481
+
482
+ def test_duplicate_ids_fix(self):
483
+
484
+ # Use the test SVG file directly
485
+ test_svg = 'test_data/svg/core/breadboard/duplicate_ids_breadboard.svg'
486
+
487
+ with tempfile.TemporaryDirectory() as temp_dir:
488
+ # Copy SVG to temp directory
489
+ temp_svg = os.path.join(temp_dir, 'temp_duplicate_ids.svg')
490
+ shutil.copy(test_svg, temp_svg)
491
+
492
+ # Parse the SVG
493
+ svg_doc = etree.parse(temp_svg)
494
+
495
+ # Create the checker directly
496
+ checker = SVGIdsChecker(svg_doc, [])
497
+
498
+ # Test initial state - should have duplicate ID errors
499
+ initial_errors, initial_warnings = checker.check()
500
+ self.assertGreater(initial_errors, 0, "Should initially have duplicate ID errors")
501
+
502
+ # Debug: Print the SVG content before fix to understand what we're working with
503
+ with open(temp_svg, 'r') as f:
504
+ before_content = f.read()
505
+ print(f"DEBUG: SVG content before fix:")
506
+ print(before_content)
507
+
508
+ # Apply fix directly
509
+ fix_results = checker.fix(temp_svg)
510
+
511
+ # Debug output
512
+ print(f"DEBUG: fix_results={fix_results}")
513
+ print(f"DEBUG: fixes_count={checker.get_fixes_count()}")
514
+ for fix in checker.fixes:
515
+ print(f"DEBUG: Fix: {fix.message}")
516
+
517
+ self.assertIsInstance(fix_results, list, "Fix method should return list of FixResult objects")
518
+ self.assertGreater(len(fix_results), 0, "Should have applied some fixes - there are consecutive duplicate label IDs to fix")
519
+ self.assertEqual(len(fix_results), checker.get_fixes_count(), "Fix results count should match fixes count")
520
+
521
+ def test_date_format_valid_iso(self):
522
+ """Test that valid ISO dates pass without changes"""
523
+ fzp_xml = """<?xml version="1.0"?>
524
+ <module fritzingVersion="1.0.0" moduleId="test">
525
+ <version>1.0</version>
526
+ <date>2024-06-13</date>
527
+ </module>"""
528
+
529
+ from .fzp_checkers import FZPDateFormatChecker
530
+
531
+ fzp_doc = etree.fromstring(fzp_xml)
532
+ checker = FZPDateFormatChecker(fzp_doc)
533
+ errors, warnings = checker.check()
534
+
535
+ self.assertEqual(errors, 0, "Valid ISO date should not produce errors")
536
+ self.assertEqual(warnings, 0, "Valid ISO date should not produce warnings")
537
+ self.assertEqual(len(checker.fixes), 0, "Valid ISO date should not need fixes")
538
+
539
+ # Date should remain unchanged
540
+ date_element = fzp_doc.find('.//date')
541
+ self.assertEqual(date_element.text, "2024-06-13")
542
+
543
+ def test_date_format_common_format_check(self):
544
+ """Test that common date format 'Thu Jun 13 2024' is detected by check method"""
545
+ fzp_xml = """<?xml version="1.0"?>
546
+ <module fritzingVersion="1.0.0" moduleId="test">
547
+ <version>1.0</version>
548
+ <date>Thu Jun 13 2024</date>
549
+ </module>"""
550
+
551
+ from .fzp_checkers import FZPDateFormatChecker
552
+
553
+ fzp_doc = etree.fromstring(fzp_xml)
554
+ checker = FZPDateFormatChecker(fzp_doc)
555
+ errors, warnings = checker.check()
556
+
557
+ self.assertEqual(errors, 0, "Fixable date should not produce errors")
558
+ self.assertEqual(warnings, 1, "Fixable date should produce one warning")
559
+ self.assertEqual(len(checker.fixes), 0, "Check method should not apply fixes")
560
+
561
+ # Date should remain unchanged after check
562
+ date_element = fzp_doc.find('.//date')
563
+ self.assertEqual(date_element.text, "Thu Jun 13 2024", "Date should be unchanged by check method")
564
+
565
+ # Check warning message
566
+ self.assertIn("2024-06-13", checker.issues[0].message)
567
+
568
+ def test_date_format_fix_method(self):
569
+ """Test that fix method properly converts date format"""
570
+ import tempfile
571
+ fzp_content = """<?xml version="1.0"?>
572
+ <module fritzingVersion="1.0.0" moduleId="test">
573
+ <version>1.0</version>
574
+ <date>Thu Jun 13 2024</date>
575
+ </module>"""
576
+
577
+ from .fzp_checkers import FZPDateFormatChecker
578
+
579
+ # Create temporary file
580
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.fzp', delete=False) as temp_file:
581
+ temp_file.write(fzp_content)
582
+ temp_filename = temp_file.name
583
+
584
+ try:
585
+ # Parse and check
586
+ fzp_doc = etree.fromstring(fzp_content)
587
+ checker = FZPDateFormatChecker(fzp_doc)
588
+
589
+ # Apply fixes
590
+ fixes = checker.fix(temp_filename)
591
+
592
+ # Read the modified file
593
+ with open(temp_filename, 'r') as f:
594
+ modified_content = f.read()
595
+
596
+ # Check that the date was converted
597
+ self.assertIn("2024-06-13", modified_content)
598
+ self.assertNotIn("Thu Jun 13 2024", modified_content)
599
+
600
+ # Check fix results
601
+ self.assertEqual(len(fixes), 1, "Should have applied one fix")
602
+ self.assertIn("Thu Jun 13 2024", fixes[0].message)
603
+ self.assertIn("2024-06-13", fixes[0].message)
604
+
605
+ finally:
606
+ # Clean up
607
+ os.unlink(temp_filename)
608
+
609
+ def test_date_format_japanese_format(self):
610
+ """Test Japanese date format conversion"""
611
+ fzp_xml = """<?xml version="1.0"?>
612
+ <module fritzingVersion="1.0.0" moduleId="test">
613
+ <version>1.0</version>
614
+ <date>木 3 31 2016</date>
615
+ </module>"""
616
+
617
+ from .fzp_checkers import FZPDateFormatChecker
618
+
619
+ fzp_doc = etree.fromstring(fzp_xml)
620
+ checker = FZPDateFormatChecker(fzp_doc)
621
+ errors, warnings = checker.check()
622
+
623
+ self.assertEqual(errors, 0, "Japanese date should be fixable")
624
+ self.assertEqual(warnings, 1, "Should produce one warning for fixable date")
625
+ self.assertEqual(len(checker.fixes), 0, "Check method should not apply fixes")
626
+
627
+ # Date should remain unchanged after check
628
+ date_element = fzp_doc.find('.//date')
629
+ self.assertEqual(date_element.text, "木 3 31 2016", "Date should remain unchanged by check method")
630
+
631
+ def test_date_format_ambiguous_dates(self):
632
+ """Test ambiguous date formats (DD/MM vs MM/DD)"""
633
+ test_cases = [
634
+ ("25/06/2024", "2024-06-25"), # Clearly DD/MM (25 > 12)
635
+ ("06/25/2024", "2024-06-25"), # Clearly MM/DD (25 > 12)
636
+ ("05/06/2024", "2024-06-05"), # Ambiguous, defaults to DD/MM
637
+ ]
638
+
639
+ from .fzp_checkers import FZPDateFormatChecker
640
+
641
+ for input_date, expected_output in test_cases:
642
+ with self.subTest(input_date=input_date):
643
+ fzp_xml = f"""<?xml version="1.0"?>
644
+ <module fritzingVersion="1.0.0" moduleId="test">
645
+ <version>1.0</version>
646
+ <date>{input_date}</date>
647
+ </module>"""
648
+
649
+ fzp_doc = etree.fromstring(fzp_xml)
650
+ checker = FZPDateFormatChecker(fzp_doc)
651
+ errors, warnings = checker.check()
652
+
653
+ self.assertEqual(errors, 0, f"Date {input_date} should be fixable")
654
+ self.assertEqual(warnings, 1, f"Date {input_date} should produce one warning")
655
+
656
+ # Date should remain unchanged after check
657
+ date_element = fzp_doc.find('.//date')
658
+ self.assertEqual(date_element.text, input_date,
659
+ f"Date {input_date} should remain unchanged by check method")
660
+
661
+ def test_date_format_invalid_dates(self):
662
+ """Test invalid date formats"""
663
+ invalid_dates = [
664
+ "Not a date",
665
+ "32/13/2024",
666
+ "June 32 2024",
667
+ "2024-13-01",
668
+ "",
669
+ ]
670
+
671
+ from .fzp_checkers import FZPDateFormatChecker
672
+
673
+ for invalid_date in invalid_dates:
674
+ with self.subTest(invalid_date=invalid_date):
675
+ fzp_xml = f"""<?xml version="1.0"?>
676
+ <module fritzingVersion="1.0.0" moduleId="test">
677
+ <version>1.0</version>
678
+ <date>{invalid_date}</date>
679
+ </module>"""
680
+
681
+ fzp_doc = etree.fromstring(fzp_xml)
682
+ checker = FZPDateFormatChecker(fzp_doc)
683
+ errors, warnings = checker.check()
684
+
685
+ # Should produce either an error or warning
686
+ total_issues = errors + warnings
687
+ self.assertGreater(total_issues, 0, f"Invalid date '{invalid_date}' should produce issues")
688
+
689
+ def test_date_format_empty_date(self):
690
+ """Test empty date element"""
691
+ fzp_xml = """<?xml version="1.0"?>
692
+ <module fritzingVersion="1.0.0" moduleId="test">
693
+ <version>1.0</version>
694
+ <date></date>
695
+ </module>"""
696
+
697
+ from .fzp_checkers import FZPDateFormatChecker
698
+
699
+ fzp_doc = etree.fromstring(fzp_xml)
700
+ checker = FZPDateFormatChecker(fzp_doc)
701
+ errors, warnings = checker.check()
702
+
703
+ self.assertEqual(warnings, 1, "Empty date should produce one warning")
704
+ self.assertIn("empty", checker.issues[0].message.lower())
705
+
706
+ def test_date_format_checker_name_and_description(self):
707
+ """Test checker metadata"""
708
+ from .fzp_checkers import FZPDateFormatChecker
709
+
710
+ self.assertEqual(FZPDateFormatChecker.get_name(), "date_format")
711
+ self.assertIn("date format", FZPDateFormatChecker.get_description().lower())
712
+ self.assertIn("ISO format", FZPDateFormatChecker.get_description())
713
+
714
+ def test_connector_numbering_valid(self):
715
+ self.run_checker('connector_numbering_valid.fzp.test',
716
+ ['connector_numbering'],
717
+ [],
718
+ 0,
719
+ None)
720
+
721
+ def test_connector_numbering_invalid(self):
722
+ self.run_checker('connector_numbering_invalid.fzp.test',
723
+ ['connector_numbering'],
724
+ [],
725
+ 0,
726
+ None,
727
+ 3) # 3 warnings for mismatched connector numbering
728
+
729
+ def test_copper_color_valid_standard(self):
730
+ self.run_checker('copper_color_valid_standard.fzp.test',
731
+ [],
732
+ ['copper_layer_content'],
733
+ 0,
734
+ None)
735
+
736
+ def test_copper_color_valid_uppercase(self):
737
+ self.run_checker('copper_color_valid_uppercase.fzp.test',
738
+ [],
739
+ ['copper_layer_content'],
740
+ 0,
741
+ None)
742
+
743
+ def test_copper_color_valid_legacy(self):
744
+ self.run_checker('copper_color_valid_legacy.fzp.test',
745
+ [],
746
+ ['copper_layer_content'],
747
+ 0,
748
+ None)
749
+
750
+ def test_copper_color_valid_inherited(self):
751
+ self.run_checker('copper_color_valid_inherited.fzp.test',
752
+ [],
753
+ ['copper_layer_content'],
754
+ 0,
755
+ None)
756
+
757
+ def test_copper_color_valid_none(self):
758
+ self.run_checker('copper_color_valid_none.fzp.test',
759
+ [],
760
+ ['copper_layer_content'],
761
+ 0,
762
+ None)
763
+
764
+ def test_copper_color_valid_nested_copper(self):
765
+ self.run_checker('copper_color_valid_nested_copper.fzp.test',
766
+ [],
767
+ ['copper_layer_content'],
768
+ 0,
769
+ None)
770
+
771
+ def test_copper_color_invalid_silkscreen_nested(self):
772
+ self.run_checker('copper_color_invalid_silkscreen_nested.fzp.test',
773
+ [],
774
+ ['copper_layer_content'],
775
+ 2,
776
+ None)
777
+
778
+ def test_copper_color_invalid_wrong_color(self):
779
+ self.run_checker('copper_color_invalid_wrong_color.fzp.test',
780
+ [],
781
+ ['copper_layer_content'],
782
+ 1,
783
+ None)
784
+
785
+ def test_copper_color_invalid_white_stroke(self):
786
+ self.run_checker('copper_color_invalid_white_stroke.fzp.test',
787
+ [],
788
+ ['copper_layer_content'],
789
+ 1,
790
+ None)
791
+
792
+ def test_copper_color_invalid_mixed(self):
793
+ self.run_checker('copper_color_invalid_mixed.fzp.test',
794
+ [],
795
+ ['copper_layer_content'],
796
+ 2,
797
+ None)
798
+
799
+ def test_copper_color_invalid_style_attr(self):
800
+ self.run_checker('copper_color_invalid_style_attr.fzp.test',
801
+ [],
802
+ ['copper_layer_content'],
803
+ 1,
804
+ None)
805
+
806
+ def test_no_layer_valid(self):
807
+ """Test that SVG with elements inside proper layer group passes."""
808
+ self.run_checker('no_layer_valid.fzp.test',
809
+ [],
810
+ ['no_layer'],
811
+ 0,
812
+ None)
813
+
814
+ def test_no_layer_with_namedview(self):
815
+ """Test that sodipodi:namedview elements are correctly skipped"""
816
+ self.run_checker('no_layer_namedview.fzp.test',
817
+ [],
818
+ ['no_layer'],
819
+ 0,
820
+ None)
821
+
822
+ def test_no_layer_invalid(self):
823
+ """Test that SVG with elements outside any layer group is detected"""
824
+ self.run_checker('no_layer_invalid.fzp.test',
825
+ [],
826
+ ['no_layer'],
827
+ 1,
828
+ None)
829
+
830
+ def test_template_svgs_not_reported_missing(self):
831
+ """Test that template SVGs (generic_ic_*, dip_*, etc.) are not reported as missing"""
832
+ self.run_checker('template_svgs.fzp.test',
833
+ [],
834
+ [],
835
+ 0,
836
+ None)
837
+
838
+ if __name__ == '__main__':
839
+ unittest.main()