Claude-skill-registry graphics-test-visualizer
Visualize graphics output from unit tests to verify visual correctness of shapes, paths, and rendering
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/graphics-test-visualizer" ~/.claude/skills/majiayu000-claude-skill-registry-graphics-test-visualizer && rm -rf "$T"
manifest:
skills/data/graphics-test-visualizer/SKILL.mdsource content
Graphics Test Visualizer
Visualize graphics output from unit tests to verify visual correctness of shapes, paths, and rendering.
Capabilities
-
Test Visualization
- Render graphics paths to image files
- Generate visual snapshots from tests
- Create side-by-side comparisons
- Export test output as PNG/PDF
-
Snapshot Testing
- Capture baseline images
- Compare against reference images
- Detect visual regressions
- Generate diff images showing changes
-
Interactive Previews
- Generate HTML preview galleries
- Create test result dashboards
- Show multiple test cases together
- Annotate images with test metadata
-
Debugging Visualization
- Highlight path construction steps
- Show geometry calculations visually
- Overlay debug information
- Animate path drawing
-
Documentation Generation
- Create visual API documentation
- Generate example galleries
- Build shape catalogs
- Export graphics samples
Workflow
When invoked, this skill will:
- Scan: Find graphics test files
- Extract: Identify path creation code
- Render: Generate visual output
- Compare: Check against baselines (if exist)
- Report: Create HTML gallery or test report
Usage Instructions
When the user invokes this skill:
-
Ask what to visualize:
- All graphics tests
- Specific test class/method
- Failed tests only
- New tests without baselines
-
Choose output format:
- HTML gallery
- Individual PNG files
- PDF catalog
- Snapshot test files
-
Run tests and capture graphics
-
Generate visualizations
-
Create report
Project-Specific Context
Your graphics test files:
GraphicPetalTests.swiftGraphicCircleTests.swiftGraphicKiteTests.swiftGraphicHistogramTests.swiftGraphicLineTests.swiftArrowHeadTests.swift
Test Visualization Patterns
1. Render NSBezierPath to Image
extension NSBezierPath { func renderToImage( size: CGSize = CGSize(width: 400, height: 400), strokeColor: NSColor = .black, fillColor: NSColor? = nil, lineWidth: CGFloat = 1.0, backgroundColor: NSColor = .white ) -> NSImage { let image = NSImage(size: size) image.lockFocus() // Draw background backgroundColor.setFill() NSRect(origin: .zero, size: size).fill() // Draw path self.lineWidth = lineWidth strokeColor.setStroke() if let fillColor = fillColor { fillColor.setFill() self.fill() } self.stroke() image.unlockFocus() return image } func saveToPNG(at url: URL, size: CGSize = CGSize(width: 400, height: 400)) { let image = renderToImage(size: size) if let tiffData = image.tiffRepresentation, let bitmapImage = NSBitmapImageRep(data: tiffData), let pngData = bitmapImage.representation( using: .png, properties: [:] ) { try? pngData.write(to: url) } } }
2. Enhanced Test with Visualization
final class GraphicPetalTests: XCTestCase { func testPetalShape() throws { // Create test geometry controller let controller = MockGeometryController() // Create petal let petal = GraphicPetal( controller: controller, forIncrement: 0, forValue: NSNumber(value: 50.0) ) // Get the path let path = try XCTUnwrap(petal?.drawingPath) // Verify path properties XCTAssertFalse(path.isEmpty) // Visualize for debugging #if DEBUG visualizeTest( path: path, testName: "testPetalShape", fillColor: .systemBlue ) #endif } private func visualizeTest( path: NSBezierPath, testName: String, strokeColor: NSColor = .black, fillColor: NSColor? = nil ) { let outputURL = testOutputDirectory() .appendingPathComponent("\(testName).png") path.renderToImage( strokeColor: strokeColor, fillColor: fillColor ).saveToPNG(at: outputURL) print("📊 Test visualization saved: \(outputURL.path)") } private func testOutputDirectory() -> URL { let baseURL = FileManager.default .temporaryDirectory .appendingPathComponent("PaleoRoseTestOutput", isDirectory: true) try? FileManager.default.createDirectory( at: baseURL, withIntermediateDirectories: true ) return baseURL } }
3. Snapshot Testing
import XCTest final class GraphicSnapshotTests: XCTestCase { func testPetalSnapshot() throws { let controller = MockGeometryController() let petal = try XCTUnwrap( GraphicPetal( controller: controller, forIncrement: 0, forValue: NSNumber(value: 50.0) ) ) let path = try XCTUnwrap(petal.drawingPath) let image = path.renderToImage(fillColor: .systemBlue) // Compare against baseline try assertImageMatches( image, named: "petal-increment0-value50", testName: testName ) } private func assertImageMatches( _ image: NSImage, named: String, testName: String, tolerance: CGFloat = 0.02 // 2% difference allowed ) throws { let baselineURL = snapshotsDirectory() .appendingPathComponent("\(named).png") // If baseline doesn't exist, create it if !FileManager.default.fileExists(atPath: baselineURL.path) { print("📸 Creating baseline snapshot: \(named)") image.saveToPNG(at: baselineURL) return } // Load baseline guard let baselineImage = NSImage(contentsOf: baselineURL) else { XCTFail("Failed to load baseline image: \(named)") return } // Compare images let difference = compareImages(image, baselineImage) if difference > tolerance { // Save failure artifacts let failureURL = failuresDirectory() .appendingPathComponent("\(named)-actual.png") let diffURL = failuresDirectory() .appendingPathComponent("\(named)-diff.png") image.saveToPNG(at: failureURL) createDiffImage(image, baselineImage).saveToPNG(at: diffURL) XCTFail( """ Snapshot mismatch for '\(named)' Difference: \(difference * 100)% (tolerance: \(tolerance * 100)%) Actual: \(failureURL.path) Diff: \(diffURL.path) """ ) } } private func compareImages( _ image1: NSImage, _ image2: NSImage ) -> CGFloat { // Pixel-by-pixel comparison // Return percentage difference (0.0 = identical, 1.0 = completely different) guard let data1 = image1.tiffRepresentation, let data2 = image2.tiffRepresentation, let rep1 = NSBitmapImageRep(data: data1), let rep2 = NSBitmapImageRep(data: data2), rep1.size == rep2.size else { return 1.0 // Treat incomparable images as completely different } var differentPixels = 0 let totalPixels = rep1.pixelsWide * rep1.pixelsHigh for y in 0..<rep1.pixelsHigh { for x in 0..<rep1.pixelsWide { let color1 = rep1.colorAt(x: x, y: y) let color2 = rep2.colorAt(x: x, y: y) if !colorsAreEqual(color1, color2) { differentPixels += 1 } } } return CGFloat(differentPixels) / CGFloat(totalPixels) } private func colorsAreEqual( _ color1: NSColor?, _ color2: NSColor?, tolerance: CGFloat = 0.01 ) -> Bool { guard let c1 = color1?.usingColorSpace(.deviceRGB), let c2 = color2?.usingColorSpace(.deviceRGB) else { return false } let rDiff = abs(c1.redComponent - c2.redComponent) let gDiff = abs(c1.greenComponent - c2.greenComponent) let bDiff = abs(c1.blueComponent - c2.blueComponent) let aDiff = abs(c1.alphaComponent - c2.alphaComponent) return rDiff < tolerance && gDiff < tolerance && bDiff < tolerance && aDiff < tolerance } private func createDiffImage( _ actual: NSImage, _ baseline: NSImage ) -> NSImage { let size = actual.size let diffImage = NSImage(size: size) diffImage.lockFocus() // Draw side-by-side comparison let rect1 = NSRect(x: 0, y: 0, width: size.width / 2, height: size.height) let rect2 = NSRect(x: size.width / 2, y: 0, width: size.width / 2, height: size.height) baseline.draw(in: rect1) actual.draw(in: rect2) // Draw dividing line NSColor.red.setStroke() NSBezierPath.strokeLine( from: NSPoint(x: size.width / 2, y: 0), to: NSPoint(x: size.width / 2, y: size.height) ) diffImage.unlockFocus() return diffImage } private func snapshotsDirectory() -> URL { URL(fileURLWithPath: #file) .deletingLastPathComponent() .appendingPathComponent("__Snapshots__", isDirectory: true) } private func failuresDirectory() -> URL { FileManager.default.temporaryDirectory .appendingPathComponent("SnapshotFailures", isDirectory: true) } }
HTML Gallery Generation
Test Results Gallery
struct TestResultsGallery { let testResults: [GraphicTestResult] func generateHTML() -> String { """ <!DOCTYPE html> <html> <head> <title>Graphics Test Results - PaleoRose</title> <style> body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; margin: 0; padding: 20px; background: #f5f5f5; } .header { background: white; padding: 20px; margin-bottom: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .test-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; } .test-card { background: white; border-radius: 8px; padding: 15px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .test-card.passed { border-left: 4px solid #34c759; } .test-card.failed { border-left: 4px solid #ff3b30; } .test-image { width: 100%; height: auto; border: 1px solid #ddd; border-radius: 4px; } .test-name { font-weight: 600; margin: 10px 0 5px 0; } .test-status { font-size: 12px; color: #666; } </style> </head> <body> <div class="header"> <h1>🎨 PaleoRose Graphics Test Results</h1> <p>Generated: \(Date())</p> <p>Total Tests: \(testResults.count) | Passed: \(passedCount) | Failed: \(failedCount)</p> </div> <div class="test-grid"> \(testResults.map(testCardHTML).joined()) </div> </body> </html> """ } private func testCardHTML(_ result: GraphicTestResult) -> String { """ <div class="test-card \(result.passed ? "passed" : "failed")"> <img src="\(result.imageURL.path)" class="test-image" alt="\(result.testName)"> <div class="test-name">\(result.testName)</div> <div class="test-status">\(result.status)</div> </div> """ } private var passedCount: Int { testResults.filter(\.passed).count } private var failedCount: Int { testResults.filter { !$0.passed }.count } } struct GraphicTestResult { let testName: String let imageURL: URL let passed: Bool let status: String }
Debug Visualization
Step-by-Step Path Construction
extension NSBezierPath { func renderConstructionSteps( size: CGSize = CGSize(width: 400, height: 400), outputDirectory: URL ) { var stepPaths: [NSBezierPath] = [] let currentPath = NSBezierPath() // Extract all path elements var elementIndex = 0 for i in 0..<elementCount { let points = UnsafeMutablePointer<NSPoint>.allocate(capacity: 3) defer { points.deallocate() } let elementType = element(at: i, associatedPoints: points) // Add element to current path switch elementType { case .moveTo: currentPath.move(to: points[0]) case .lineTo: currentPath.line(to: points[0]) case .curveTo: currentPath.curve( to: points[2], controlPoint1: points[0], controlPoint2: points[1] ) case .closePath: currentPath.close() @unknown default: break } // Save snapshot let snapshot = currentPath.copy() as! NSBezierPath stepPaths.append(snapshot) } // Render each step for (index, path) in stepPaths.enumerated() { let url = outputDirectory .appendingPathComponent("step_\(String(format: "%03d", index)).png") path.saveToPNG(at: url, size: size) } } }
Annotated Geometry Visualization
func renderAnnotatedPetal( petal: GraphicPetal, controller: GraphicGeometrySource ) -> NSImage { let size = CGSize(width: 500, height: 500) let image = NSImage(size: size) image.lockFocus() // Background NSColor.white.setFill() NSRect(origin: .zero, size: size).fill() // Draw petal petal.drawingPath?.stroke() // Annotate key points let center = CGPoint(x: 250, y: 250) // Draw center point drawPoint(center, color: .red, label: "Center") // Draw radius lines NSColor.systemBlue.withAlphaComponent(0.3).setStroke() let radiusLine = NSBezierPath() radiusLine.lineWidth = 0.5 radiusLine.setLineDash([5, 3], count: 2, phase: 0) // ... draw radius // Add text annotations drawLabel( "Max Radius: \(controller.maxRadius)", at: CGPoint(x: 10, y: 470) ) image.unlockFocus() return image } func drawPoint(_ point: CGPoint, color: NSColor, label: String) { color.setFill() let rect = NSRect( x: point.x - 3, y: point.y - 3, width: 6, height: 6 ) NSBezierPath(ovalIn: rect).fill() drawLabel(label, at: CGPoint(x: point.x + 5, y: point.y + 5)) } func drawLabel(_ text: String, at point: CGPoint) { let attributes: [NSAttributedString.Key: Any] = [ .font: NSFont.systemFont(ofSize: 10), .foregroundColor: NSColor.black ] (text as NSString).draw(at: point, withAttributes: attributes) }
Integration with XCTest
Custom Assertion
func XCTAssertPathMatches( _ path: NSBezierPath, named: String, file: StaticString = #file, line: UInt = #line ) { let image = path.renderToImage() // Snapshot comparison logic } func XCTAssertImageMatches( _ image: NSImage, named: String, tolerance: CGFloat = 0.02, file: StaticString = #file, line: UInt = #line ) { // Image comparison logic }
Command Line Tools
Generate Test Gallery
#!/bin/bash # generate-test-gallery.sh swift test --enable-code-coverage 2>&1 | tee test-output.txt # Run visualization generator swift run GraphicsTestVisualizer \ --output ./TestResults/gallery.html \ --snapshots ./Tests/__Snapshots__ \ --format html open ./TestResults/gallery.html
Configuration
Store visualization settings in
.graphics-test-visualizer.json:
{ "outputDirectory": "./TestResults", "imageFormat": "png", "imageSize": { "width": 400, "height": 400 }, "backgroundColor": "#FFFFFF", "generateHTML": true, "snapshotTolerance": 0.02, "saveFailureArtifacts": true, "annotateImages": true }