Approval-based snapshot testing library for Go (mirror)
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add SnapJSON API and comprehensive JSON snapshot tests

Add new SnapJSON() public API function to snapshot JSON strings with
consistent formatting and validation. This includes:
- SnapJSON(t testingT, title string, jsonStr string) function that unmarshals
and formats JSON for deterministic snapshot comparison
- Enable utter.Config.SortKeys for consistent map key ordering in all snapshots

Add comprehensive test suite with 26 new snapshot tests:
- 12 complex Go structure tests (nested data, pointers, interfaces, edge cases)
- 14 SnapJSON-specific tests covering JSON marshaling, APIs, special characters

This ensures reliable snapshot testing for both Go structures and JSON data,
with all map keys sorted for deterministic output across test runs.

ptdewey 208db5d3 f9759acb

+959 -105
+17 -17
__snapshots__/test_complex_j_s_o_n_structure.snap
··· 7 7 --- 8 8 map[string]interface{}{ 9 9 "api": map[string]interface{}{ 10 - "version": "2.0", 11 10 "endpoints": []interface{}{ 12 11 map[string]interface{}{ 12 + "auth_required": true, 13 + "method": "GET", 14 + "path": "/users", 13 15 "rate_limit": map[string]interface{}{ 14 16 "requests": 100.0, 15 17 "window": "1m", ··· 18 20 "200": map[string]interface{}{ 19 21 "description": "Success", 20 22 "schema": map[string]interface{}{ 21 - "type": "array", 22 23 "items": map[string]interface{}{ 23 - "type": "object", 24 24 "properties": map[string]interface{}{ 25 25 "id": map[string]interface{}{ 26 26 "type": "integer", ··· 29 29 "type": "string", 30 30 }, 31 31 }, 32 + "type": "object", 32 33 }, 34 + "type": "array", 33 35 }, 34 36 }, 35 37 "401": map[string]interface{}{ 36 38 "description": "Unauthorized", 37 39 }, 38 40 }, 39 - "path": "/users", 40 - "method": "GET", 41 - "auth_required": true, 42 41 }, 43 42 map[string]interface{}{ 44 43 "auth_required": true, 44 + "method": "POST", 45 + "path": "/users/{id}", 45 46 "rate_limit": map[string]interface{}{ 46 47 "requests": 50.0, 47 48 "window": "1m", 48 49 }, 49 - "path": "/users/{id}", 50 - "method": "POST", 51 50 }, 52 51 }, 53 52 "models": map[string]interface{}{ 54 53 "User": map[string]interface{}{ 55 54 "properties": map[string]interface{}{ 56 55 "created_at": map[string]interface{}{ 57 - "type": "string", 58 56 "format": "date-time", 57 + "type": "string", 58 + }, 59 + "email": map[string]interface{}{ 60 + "format": "email", 61 + "type": "string", 62 + }, 63 + "id": map[string]interface{}{ 64 + "type": "integer", 59 65 }, 60 66 "roles": map[string]interface{}{ 61 - "type": "array", 62 67 "items": map[string]interface{}{ 63 68 "type": "string", 64 69 }, 65 - }, 66 - "id": map[string]interface{}{ 67 - "type": "integer", 70 + "type": "array", 68 71 }, 69 72 "username": map[string]interface{}{ 70 73 "type": "string", 71 - }, 72 - "email": map[string]interface{}{ 73 - "type": "string", 74 - "format": "email", 75 74 }, 76 75 }, 77 76 "required": []interface{}{ ··· 81 80 }, 82 81 }, 83 82 }, 83 + "version": "2.0", 84 84 }, 85 85 }
+1 -1
__snapshots__/test_complex_nested_structure.snap
··· 26 26 }, 27 27 Metadata: map[string]interface{}{ 28 28 "language": "en", 29 + "notifications": true, 29 30 "preferences": map[string]interface{}{ 30 31 "email_frequency": "weekly", 31 32 "notifications": true, 32 33 }, 33 34 "theme": "dark", 34 - "notifications": true, 35 35 }, 36 36 }, 37 37 Tags: []string{
+6 -6
__snapshots__/test_go_struct_marshalled_to_j_s_o_n.snap
··· 6 6 version: 0.1.0 7 7 --- 8 8 map[string]interface{}{ 9 - "name": "Jane Smith", 10 - "email": "jane@example.com", 11 - "phone": "+1-555-0123", 9 + "active": true, 12 10 "address": map[string]interface{}{ 11 + "city": "San Francisco", 13 12 "street": "456 Oak Ave", 14 - "city": "San Francisco", 15 13 "zip": "94102", 16 14 }, 15 + "created_at": "2023-06-15T14:30:00Z", 16 + "email": "jane@example.com", 17 + "name": "Jane Smith", 18 + "phone": "+1-555-0123", 17 19 "tags": []interface{}{ 18 20 "vip", 19 21 "verified", 20 22 "premium", 21 23 }, 22 - "active": true, 23 - "created_at": "2023-06-15T14:30:00Z", 24 24 }
+10 -10
__snapshots__/test_j_s_o_n_array_of_objects.snap
··· 7 7 --- 8 8 []interface{}{ 9 9 map[string]interface{}{ 10 - "type": "user", 11 - "id": 1.0, 12 10 "data": map[string]interface{}{ 11 + "name": "Alice", 13 12 "role": "admin", 14 - "name": "Alice", 15 13 }, 14 + "id": 1.0, 15 + "type": "user", 16 16 }, 17 17 map[string]interface{}{ 18 - "type": "post", 19 - "id": 100.0, 20 18 "data": map[string]interface{}{ 19 + "author_id": 1.0, 21 20 "likes": 42.0, 22 21 "title": "First Post", 23 - "author_id": 1.0, 24 22 }, 23 + "id": 100.0, 24 + "type": "post", 25 25 }, 26 26 map[string]interface{}{ 27 - "type": "comment", 28 - "id": 500.0, 29 27 "data": map[string]interface{}{ 30 - "post_id": 100.0, 28 + "author_id": 2.0, 31 29 "content": "Great post!", 32 - "author_id": 2.0, 30 + "post_id": 100.0, 33 31 }, 32 + "id": 500.0, 33 + "type": "comment", 34 34 }, 35 35 }
+8 -8
__snapshots__/test_j_s_o_n_numbers.snap
··· 6 6 version: 0.1.0 7 7 --- 8 8 map[string]interface{}{ 9 + "edge_cases": map[string]interface{}{ 10 + "minus_one": -1.0, 11 + "one": 1.0, 12 + }, 9 13 "floats": map[string]interface{}{ 14 + "negative_float": -42.5, 10 15 "pi": 3.14159265359, 11 16 "scientific": 0.000123, 12 - "negative_float": -42.5, 13 17 "small": 0.0001, 14 18 }, 15 - "edge_cases": map[string]interface{}{ 16 - "one": 1.0, 17 - "minus_one": -1.0, 18 - }, 19 19 "integers": map[string]interface{}{ 20 - "zero": 0.0, 21 - "positive": 42.0, 22 - "negative": -100.0, 23 20 "large": 9.999999999999e+12.0, 21 + "negative": -100.0, 22 + "positive": 42.0, 23 + "zero": 0.0, 24 24 }, 25 25 }
+9 -9
__snapshots__/test_j_s_o_n_object.snap
··· 6 6 version: 0.1.0 7 7 --- 8 8 map[string]interface{}{ 9 + "message": nil, 10 + "status": "success", 9 11 "user": map[string]interface{}{ 10 - "roles": []interface{}{ 11 - "user", 12 - "admin", 13 - }, 14 12 "created_at": "2023-01-15T10:30:00Z", 15 - "id": 1.0, 16 - "username": "john_doe", 17 13 "email": "john@example.com", 14 + "id": 1.0, 18 15 "profile": map[string]interface{}{ 16 + "bio": "Software engineer", 19 17 "first_name": "John", 20 18 "last_name": "Doe", 21 - "bio": "Software engineer", 22 19 "verified": true, 23 20 }, 21 + "roles": []interface{}{ 22 + "user", 23 + "admin", 24 + }, 25 + "username": "john_doe", 24 26 }, 25 - "status": "success", 26 - "message": nil, 27 27 }
+2 -2
__snapshots__/test_j_s_o_n_with_mixed_arrays.snap
··· 44 44 "name": "Item 1", 45 45 }, 46 46 map[string]interface{}{ 47 - "name": "Item 2", 48 47 "id": 2.0, 48 + "name": "Item 2", 49 49 }, 50 50 map[string]interface{}{ 51 - "name": "Item 3", 52 51 "id": 3.0, 52 + "name": "Item 3", 53 53 }, 54 54 }, 55 55 }
+4 -4
__snapshots__/test_j_s_o_n_with_special_characters.snap
··· 6 6 version: 0.1.0 7 7 --- 8 8 map[string]interface{}{ 9 - "tabs": "col1\tcol2\tcol3", 10 9 "backslash": "path\\to\\file", 11 - "english": "Hello, World!", 12 - "unicode": "こんにちは 世界 🌍", 13 10 "emoji": "😀 😃 😄 😁 😆", 14 - "special_chars": "!@#$%^&*()_+-=[]{}|;:,.<>?", 11 + "english": "Hello, World!", 15 12 "escaped": "quotes: \"double\" and 'single'", 16 13 "newlines": "line1\nline2\rline3\r\nline4", 14 + "special_chars": "!@#$%^&*()_+-=[]{}|;:,.<>?", 15 + "tabs": "col1\tcol2\tcol3", 16 + "unicode": "こんにちは 世界 🌍", 17 17 }
+6 -6
__snapshots__/test_j_s_o_n_with_various_types.snap
··· 6 6 version: 0.1.0 7 7 --- 8 8 map[string]interface{}{ 9 - "float": 3.14159, 10 - "boolean_true": true, 11 - "null_value": nil, 12 9 "array": []interface{}{ 13 10 1.0, 14 11 2.0, ··· 16 13 "four", 17 14 5.5, 18 15 }, 16 + "boolean_false": false, 17 + "boolean_true": true, 19 18 "empty_array": []interface{}{ 20 19 }, 21 20 "empty_object": map[string]interface{}{ 22 21 }, 23 22 "escaped_string": "line1\nline2\ttab", 24 - "string": "hello world", 23 + "float": 3.14159, 25 24 "integer": 42.0, 26 - "boolean_false": false, 25 + "null_value": nil, 27 26 "object": map[string]interface{}{ 28 - "nested": "value", 29 27 "count": 10.0, 28 + "nested": "value", 30 29 }, 30 + "string": "hello world", 31 31 }
+14 -14
__snapshots__/test_large_j_s_o_n.snap
··· 6 6 version: 0.1.0 7 7 --- 8 8 map[string]interface{}{ 9 - "total": 1179.97, 10 - "status": "shipped", 11 9 "created_at": "2023-01-28T14:30:00Z", 12 - "shipped_at": "2023-02-01T10:00:00Z", 10 + "customer_id": 42.0, 13 11 "delivered_at": nil, 14 12 "id": 1001.0, 15 - "customer_id": 42.0, 16 13 "products": []interface{}{ 17 14 map[string]interface{}{ 15 + "description": "High-performance laptop", 16 + "id": 1.0, 17 + "in_stock": true, 18 + "name": "Laptop", 18 19 "price": 999.99, 19 20 "stock": 5.0, 20 - "in_stock": true, 21 21 "tags": []interface{}{ 22 22 "electronics", 23 23 "computers", 24 24 "laptops", 25 25 }, 26 - "id": 1.0, 27 - "name": "Laptop", 28 - "description": "High-performance laptop", 29 26 }, 30 27 map[string]interface{}{ 31 - "name": "Mouse", 32 28 "description": "Wireless mouse", 29 + "id": 2.0, 30 + "in_stock": true, 31 + "name": "Mouse", 33 32 "price": 29.99, 34 33 "stock": 50.0, 35 - "in_stock": true, 36 34 "tags": []interface{}{ 37 35 "electronics", 38 36 "accessories", 39 37 }, 40 - "id": 2.0, 41 38 }, 42 39 map[string]interface{}{ 43 40 "description": "Mechanical keyboard", 41 + "id": 3.0, 42 + "in_stock": false, 43 + "name": "Keyboard", 44 44 "price": 149.99, 45 45 "stock": 0.0, 46 - "in_stock": false, 47 46 "tags": []interface{}{ 48 47 "electronics", 49 48 "accessories", 50 49 }, 51 - "id": 3.0, 52 - "name": "Keyboard", 53 50 }, 54 51 }, 52 + "shipped_at": "2023-02-01T10:00:00Z", 53 + "status": "shipped", 54 + "total": 1179.97, 55 55 }
+3 -3
__snapshots__/test_multiple_complex_structures.snap
··· 21 21 "moderator", 22 22 }, 23 23 Metadata: map[string]interface{}{ 24 - "verified": true, 25 24 "badge": "verified", 25 + "verified": true, 26 26 }, 27 27 }, 28 28 { ··· 39 39 "user", 40 40 }, 41 41 Metadata: map[string]interface{}{ 42 - "verified": false, 43 42 "avatar": "https://example.com/bob.jpg", 43 + "verified": false, 44 44 }, 45 45 }, 46 46 { ··· 58 58 "admin", 59 59 }, 60 60 Metadata: map[string]interface{}{ 61 - "verified": true, 62 61 "account_age_days": 365, 62 + "verified": true, 63 63 }, 64 64 }, 65 65 }
+23 -23
__snapshots__/test_nested_maps_and_slices.snap
··· 6 6 version: 0.1.0 7 7 --- 8 8 map[string]interface{}{ 9 + "posts": map[string]interface{}{ 10 + "categories": []string{ 11 + "tech", 12 + "lifestyle", 13 + "news", 14 + }, 15 + "drafts": 5, 16 + "published": 42, 17 + }, 18 + "stats": map[string]interface{}{ 19 + "daily": map[string]interface{}{ 20 + "clicks": 320, 21 + "conversions": map[string]interface{}{ 22 + "by_source": map[string]int{ 23 + "organic": 25, 24 + "paid": 15, 25 + "referral": 5, 26 + }, 27 + "total": 45, 28 + }, 29 + "views": 1500, 30 + }, 31 + }, 9 32 "users": map[string]interface{}{ 10 33 "active": []map[string]interface{}{ 11 34 { ··· 23 46 { 24 47 "id": 3, 25 48 "name": "Charlie", 26 - }, 27 - }, 28 - }, 29 - "posts": map[string]interface{}{ 30 - "published": 42, 31 - "drafts": 5, 32 - "categories": []string{ 33 - "tech", 34 - "lifestyle", 35 - "news", 36 - }, 37 - }, 38 - "stats": map[string]interface{}{ 39 - "daily": map[string]interface{}{ 40 - "views": 1500, 41 - "clicks": 320, 42 - "conversions": map[string]interface{}{ 43 - "total": 45, 44 - "by_source": map[string]int{ 45 - "paid": 15, 46 - "referral": 5, 47 - "organic": 25, 48 - }, 49 49 }, 50 50 }, 51 51 },
+30
__snapshots__/test_snap_j_s_o_n_array_of_objects.snap
··· 1 + --- 2 + title: SnapJSON Array of Objects 3 + test_name: TestSnapJSONArrayOfObjects 4 + file_path: 5 + func_name: 6 + version: 0.1.0 7 + --- 8 + []interface{}{ 9 + map[string]interface{}{ 10 + "id": 1.0, 11 + "likes": 42.0, 12 + "title": "First Post", 13 + "type": "post", 14 + "views": 150.0, 15 + }, 16 + map[string]interface{}{ 17 + "id": 2.0, 18 + "likes": 75.0, 19 + "title": "Second Post", 20 + "type": "post", 21 + "views": 280.0, 22 + }, 23 + map[string]interface{}{ 24 + "id": 3.0, 25 + "likes": 120.0, 26 + "title": "Third Post", 27 + "type": "post", 28 + "views": 450.0, 29 + }, 30 + }
+13
__snapshots__/test_snap_j_s_o_n_basic.snap
··· 1 + --- 2 + title: SnapJSON Basic Object 3 + test_name: TestSnapJSONBasic 4 + file_path: 5 + func_name: 6 + version: 0.1.0 7 + --- 8 + map[string]interface{}{ 9 + "age": 30.0, 10 + "email": "john@example.com", 11 + "name": "John Doe", 12 + "verified": true, 13 + }
+17
__snapshots__/test_snap_j_s_o_n_compact_format.snap
··· 1 + --- 2 + title: SnapJSON Compact Format 3 + test_name: TestSnapJSONCompactFormat 4 + file_path: 5 + func_name: 6 + version: 0.1.0 7 + --- 8 + map[string]interface{}{ 9 + "id": 1.0, 10 + "in_stock": true, 11 + "name": "Product", 12 + "price": 99.99, 13 + "tags": []interface{}{ 14 + "electronics", 15 + "gadgets", 16 + }, 17 + }
+43
__snapshots__/test_snap_j_s_o_n_complex_a_p_i.snap
··· 1 + --- 2 + title: SnapJSON Complex API Response 3 + test_name: TestSnapJSONComplexAPI 4 + file_path: 5 + func_name: 6 + version: 0.1.0 7 + --- 8 + map[string]interface{}{ 9 + "code": 200.0, 10 + "data": map[string]interface{}{ 11 + "pagination": map[string]interface{}{ 12 + "page": 1.0, 13 + "per_page": 10.0, 14 + "total": 3.0, 15 + "total_pages": 1.0, 16 + }, 17 + "users": []interface{}{ 18 + map[string]interface{}{ 19 + "active": true, 20 + "department": "Engineering", 21 + "id": 1.0, 22 + "name": "Alice", 23 + "role": "admin", 24 + }, 25 + map[string]interface{}{ 26 + "active": true, 27 + "department": "Sales", 28 + "id": 2.0, 29 + "name": "Bob", 30 + "role": "user", 31 + }, 32 + map[string]interface{}{ 33 + "active": false, 34 + "department": "Marketing", 35 + "id": 3.0, 36 + "name": "Charlie", 37 + "role": "user", 38 + }, 39 + }, 40 + }, 41 + "status": "success", 42 + "timestamp": "2023-11-18T21:45:30Z", 43 + }
+23
__snapshots__/test_snap_j_s_o_n_empty_structures.snap
··· 1 + --- 2 + title: SnapJSON Empty Structures 3 + test_name: TestSnapJSONEmptyStructures 4 + file_path: 5 + func_name: 6 + version: 0.1.0 7 + --- 8 + map[string]interface{}{ 9 + "empty_array": []interface{}{ 10 + }, 11 + "empty_object": map[string]interface{}{ 12 + }, 13 + "empty_string": "", 14 + "false_value": false, 15 + "nested": map[string]interface{}{ 16 + "also_empty": map[string]interface{}{ 17 + }, 18 + "empty": []interface{}{ 19 + }, 20 + }, 21 + "null_value": nil, 22 + "zero": 0.0, 23 + }
+105
__snapshots__/test_snap_j_s_o_n_large_nested_structure.snap
··· 1 + --- 2 + title: SnapJSON Large Nested Structure 3 + test_name: TestSnapJSONLargeNestedStructure 4 + file_path: 5 + func_name: 6 + version: 0.1.0 7 + --- 8 + map[string]interface{}{ 9 + "organization": map[string]interface{}{ 10 + "departments": []interface{}{ 11 + map[string]interface{}{ 12 + "manager": "Alice", 13 + "name": "Engineering", 14 + "teams": []interface{}{ 15 + map[string]interface{}{ 16 + "lead": "John", 17 + "members": []interface{}{ 18 + map[string]interface{}{ 19 + "id": 1.0, 20 + "level": "senior", 21 + "name": "John", 22 + }, 23 + map[string]interface{}{ 24 + "id": 2.0, 25 + "level": "mid", 26 + "name": "Jane", 27 + }, 28 + }, 29 + "name": "Backend", 30 + "projects": []interface{}{ 31 + map[string]interface{}{ 32 + "id": "proj_1", 33 + "name": "API Service", 34 + "status": "active", 35 + }, 36 + map[string]interface{}{ 37 + "id": "proj_2", 38 + "name": "Database Optimization", 39 + "status": "planning", 40 + }, 41 + }, 42 + }, 43 + map[string]interface{}{ 44 + "lead": "Bob", 45 + "members": []interface{}{ 46 + map[string]interface{}{ 47 + "id": 3.0, 48 + "level": "senior", 49 + "name": "Bob", 50 + }, 51 + map[string]interface{}{ 52 + "id": 4.0, 53 + "level": "junior", 54 + "name": "Carol", 55 + }, 56 + }, 57 + "name": "Frontend", 58 + "projects": []interface{}{ 59 + map[string]interface{}{ 60 + "id": "proj_3", 61 + "name": "Web App", 62 + "status": "active", 63 + }, 64 + }, 65 + }, 66 + }, 67 + }, 68 + map[string]interface{}{ 69 + "manager": "Charlie", 70 + "name": "Sales", 71 + "teams": []interface{}{ 72 + map[string]interface{}{ 73 + "lead": "Dave", 74 + "members": []interface{}{ 75 + map[string]interface{}{ 76 + "id": 5.0, 77 + "level": "senior", 78 + "name": "Dave", 79 + }, 80 + map[string]interface{}{ 81 + "id": 6.0, 82 + "level": "mid", 83 + "name": "Eve", 84 + }, 85 + }, 86 + "name": "Enterprise", 87 + "projects": []interface{}{ 88 + }, 89 + }, 90 + }, 91 + }, 92 + }, 93 + "id": "org_123", 94 + "metadata": map[string]interface{}{ 95 + "employees": 150.0, 96 + "founded": "2020", 97 + "locations": []interface{}{ 98 + "USA", 99 + "EU", 100 + "APAC", 101 + }, 102 + }, 103 + "name": "TechCorp", 104 + }, 105 + }
+42
__snapshots__/test_snap_j_s_o_n_mixed_types.snap
··· 1 + --- 2 + title: SnapJSON Mixed Types 3 + test_name: TestSnapJSONMixedTypes 4 + file_path: 5 + func_name: 6 + version: 0.1.0 7 + --- 8 + map[string]interface{}{ 9 + "complex": []interface{}{ 10 + map[string]interface{}{ 11 + "id": 1.0, 12 + "type": "user", 13 + }, 14 + map[string]interface{}{ 15 + "id": 100.0, 16 + "type": "post", 17 + }, 18 + []interface{}{ 19 + 1.0, 20 + 2.0, 21 + 3.0, 22 + }, 23 + "string", 24 + nil, 25 + }, 26 + "mixed_array": []interface{}{ 27 + "string", 28 + 123.0, 29 + 45.67, 30 + true, 31 + false, 32 + nil, 33 + map[string]interface{}{ 34 + "nested": "object", 35 + }, 36 + []interface{}{ 37 + 1.0, 38 + 2.0, 39 + 3.0, 40 + }, 41 + }, 42 + }
+85
__snapshots__/test_snap_j_s_o_n_real_world_example.snap
··· 1 + --- 2 + title: SnapJSON Real World Example 3 + test_name: TestSnapJSONRealWorldExample 4 + file_path: 5 + func_name: 6 + version: 0.1.0 7 + --- 8 + map[string]interface{}{ 9 + "data": map[string]interface{}{ 10 + "product": map[string]interface{}{ 11 + "description": "High-quality wireless headphones with noise cancellation", 12 + "id": "prod_12345", 13 + "inventory": map[string]interface{}{ 14 + "available": 425.0, 15 + "damaged": 25.0, 16 + "reserved": 50.0, 17 + "total": 500.0, 18 + }, 19 + "name": "Premium Wireless Headphones", 20 + "price": map[string]interface{}{ 21 + "amount": 199.99, 22 + "currency": "USD", 23 + "discount": 10.0, 24 + "final_price": 179.99, 25 + }, 26 + "ratings": map[string]interface{}{ 27 + "average": 4.5, 28 + "breakdown": map[string]interface{}{ 29 + "1": 20.0, 30 + "2": 30.0, 31 + "3": 100.0, 32 + "4": 350.0, 33 + "5": 750.0, 34 + }, 35 + "count": 1250.0, 36 + }, 37 + "reviews": []interface{}{ 38 + map[string]interface{}{ 39 + "content": "Great sound quality and comfortable to wear.", 40 + "created_at": "2023-11-15T10:30:00Z", 41 + "helpful": 25.0, 42 + "id": "rev_001", 43 + "rating": 5.0, 44 + "title": "Excellent product!", 45 + "user": "john_doe", 46 + }, 47 + map[string]interface{}{ 48 + "content": "Works well, could be cheaper.", 49 + "created_at": "2023-11-10T14:20:00Z", 50 + "helpful": 12.0, 51 + "id": "rev_002", 52 + "rating": 4.0, 53 + "title": "Good but pricey", 54 + "user": "jane_smith", 55 + }, 56 + }, 57 + "sku": "PWH-001", 58 + "specifications": map[string]interface{}{ 59 + "battery_life": "30 hours", 60 + "colors": []interface{}{ 61 + "black", 62 + "white", 63 + "blue", 64 + }, 65 + "warranty_months": 24.0, 66 + "weight": "250g", 67 + }, 68 + }, 69 + "related_products": []interface{}{ 70 + map[string]interface{}{ 71 + "id": "prod_12346", 72 + "name": "Headphone Case", 73 + "price": 29.99, 74 + }, 75 + map[string]interface{}{ 76 + "id": "prod_12347", 77 + "name": "Audio Cable", 78 + "price": 14.99, 79 + }, 80 + }, 81 + }, 82 + "request_id": "req_abc123def456", 83 + "success": true, 84 + "timestamp": "2023-11-18T22:00:00Z", 85 + }
+13
__snapshots__/test_snap_j_s_o_n_simple_array.snap
··· 1 + --- 2 + title: SnapJSON Simple Array 3 + test_name: TestSnapJSONSimpleArray 4 + file_path: 5 + func_name: 6 + version: 0.1.0 7 + --- 8 + []interface{}{ 9 + "apple", 10 + "banana", 11 + "orange", 12 + "grape", 13 + }
+27
__snapshots__/test_snap_j_s_o_n_with_nested_objects.snap
··· 1 + --- 2 + title: SnapJSON Nested Objects 3 + test_name: TestSnapJSONWithNestedObjects 4 + file_path: 5 + func_name: 6 + version: 0.1.0 7 + --- 8 + map[string]interface{}{ 9 + "created_at": "2023-06-15T10:30:00Z", 10 + "user": map[string]interface{}{ 11 + "id": 42.0, 12 + "permissions": []interface{}{ 13 + "read", 14 + "write", 15 + "admin", 16 + }, 17 + "profile": map[string]interface{}{ 18 + "avatar": "https://example.com/avatar.jpg", 19 + "settings": map[string]interface{}{ 20 + "language": "en", 21 + "notifications": true, 22 + "theme": "dark", 23 + }, 24 + "username": "jane_smith", 25 + }, 26 + }, 27 + }
+19
__snapshots__/test_snap_j_s_o_n_with_nulls.snap
··· 1 + --- 2 + title: SnapJSON With Nulls 3 + test_name: TestSnapJSONWithNulls 4 + file_path: 5 + func_name: 6 + version: 0.1.0 7 + --- 8 + map[string]interface{}{ 9 + "category": nil, 10 + "description": nil, 11 + "id": 1.0, 12 + "metadata": map[string]interface{}{ 13 + "created": "2023-01-01", 14 + "deleted": nil, 15 + "updated": nil, 16 + }, 17 + "name": "Item", 18 + "tags": nil, 19 + }
+35
__snapshots__/test_snap_j_s_o_n_with_numbers.snap
··· 1 + --- 2 + title: SnapJSON With Numbers 3 + test_name: TestSnapJSONWithNumbers 4 + file_path: 5 + func_name: 6 + version: 0.1.0 7 + --- 8 + map[string]interface{}{ 9 + "financial": map[string]interface{}{ 10 + "expenses": 750000.75, 11 + "profit_margin": 0.2499, 12 + "revenue": 1.0000005e+06, 13 + }, 14 + "floats": []interface{}{ 15 + 0.0, 16 + 3.14, 17 + -2.5, 18 + 0.001, 19 + 0.000123, 20 + 5.67e+10.0, 21 + }, 22 + "integers": []interface{}{ 23 + 0.0, 24 + 1.0, 25 + -1.0, 26 + 42.0, 27 + -100.0, 28 + 9.999999e+06.0, 29 + }, 30 + "measurements": map[string]interface{}{ 31 + "distance": 1000.25, 32 + "temperature": -40.5, 33 + "weight": 0.5, 34 + }, 35 + }
+16
__snapshots__/test_snap_j_s_o_n_with_special_characters.snap
··· 1 + --- 2 + title: SnapJSON With Special Characters 3 + test_name: TestSnapJSONWithSpecialCharacters 4 + file_path: 5 + func_name: 6 + version: 0.1.0 7 + --- 8 + map[string]interface{}{ 9 + "escaped": "line1\nline2\ttab\rcarriage", 10 + "html": "<div class=\"container\">Content</div>", 11 + "paths": "C:\\Users\\name\\Documents\\file.txt", 12 + "quotes": "He said \"hello\" and she said 'goodbye'", 13 + "regex": "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$", 14 + "special": "!@#$%^&*()_+-=[]{}|;:',.<>?/", 15 + "unicode": "Hello 世界 🌍 مرحبا Привет", 16 + }
+1 -1
__snapshots__/test_structure_with_empty_values.snap
··· 29 29 "c", 30 30 }, 31 31 Tags: map[string]string{ 32 - "type": "test", 33 32 "env": "dev", 33 + "type": "test", 34 34 }, 35 35 OptionalID: &int(42), 36 36 Count: 3,
+1 -1
__snapshots__/test_structure_with_interface.snap
··· 69 69 }, 70 70 }, 71 71 Meta: map[string]interface{}{ 72 - "total_count": 10, 73 72 "page": 1, 74 73 "per_page": 20, 74 + "total_count": 10, 75 75 }, 76 76 }, 77 77 }
+19
freeze.go
··· 1 1 package freeze 2 2 3 3 import ( 4 + "encoding/json" 4 5 "fmt" 5 6 6 7 "github.com/kortschak/utter" ··· 15 16 // TODO: probably make this (and other things) configurable 16 17 func init() { 17 18 utter.Config.ElideType = true 19 + utter.Config.SortKeys = true 18 20 } 19 21 20 22 func SnapString(t testingT, title string, content string) { 21 23 t.Helper() 24 + snap(t, title, content) 25 + } 26 + 27 + // SnapJSON takes a JSON string, unmarshals it, and then marshals it back with 28 + // pretty-printing before snapshotting. This ensures consistent formatting and 29 + // validates the JSON structure. 30 + func SnapJSON(t testingT, title string, jsonStr string) { 31 + t.Helper() 32 + 33 + var data interface{} 34 + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { 35 + t.Error("failed to unmarshal JSON:", err) 36 + return 37 + } 38 + 39 + // Format the JSON data using the same mechanism as Snap 40 + content := formatValue(data) 22 41 snap(t, title, content) 23 42 } 24 43
+367
freeze_test.go
··· 942 942 } 943 943 944 944 // ============================================================================ 945 + // SNAPJSON FUNCTION TESTS - Serialized JSON Strings 946 + // ============================================================================ 947 + 948 + // TestSnapJSONBasic tests the SnapJSON function with basic JSON 949 + func TestSnapJSONBasic(t *testing.T) { 950 + jsonStr := `{ 951 + "name": "John Doe", 952 + "email": "john@example.com", 953 + "age": 30, 954 + "verified": true 955 + }` 956 + 957 + freeze.SnapJSON(t, "SnapJSON Basic Object", jsonStr) 958 + } 959 + 960 + // TestSnapJSONSimpleArray tests SnapJSON with simple arrays 961 + func TestSnapJSONSimpleArray(t *testing.T) { 962 + jsonStr := `[ 963 + "apple", 964 + "banana", 965 + "orange", 966 + "grape" 967 + ]` 968 + 969 + freeze.SnapJSON(t, "SnapJSON Simple Array", jsonStr) 970 + } 971 + 972 + // TestSnapJSONCompactFormat tests SnapJSON with compact (minified) JSON 973 + func TestSnapJSONCompactFormat(t *testing.T) { 974 + jsonStr := `{"id":1,"name":"Product","price":99.99,"in_stock":true,"tags":["electronics","gadgets"]}` 975 + 976 + freeze.SnapJSON(t, "SnapJSON Compact Format", jsonStr) 977 + } 978 + 979 + // TestSnapJSONWithNestedObjects tests SnapJSON with nested JSON structures 980 + func TestSnapJSONWithNestedObjects(t *testing.T) { 981 + jsonStr := `{ 982 + "user": { 983 + "id": 42, 984 + "profile": { 985 + "username": "jane_smith", 986 + "avatar": "https://example.com/avatar.jpg", 987 + "settings": { 988 + "theme": "dark", 989 + "notifications": true, 990 + "language": "en" 991 + } 992 + }, 993 + "permissions": ["read", "write", "admin"] 994 + }, 995 + "created_at": "2023-06-15T10:30:00Z" 996 + }` 997 + 998 + freeze.SnapJSON(t, "SnapJSON Nested Objects", jsonStr) 999 + } 1000 + 1001 + // TestSnapJSONComplexAPI tests SnapJSON with complex API response 1002 + func TestSnapJSONComplexAPI(t *testing.T) { 1003 + jsonStr := `{ 1004 + "status": "success", 1005 + "code": 200, 1006 + "data": { 1007 + "users": [ 1008 + { 1009 + "id": 1, 1010 + "name": "Alice", 1011 + "role": "admin", 1012 + "department": "Engineering", 1013 + "active": true 1014 + }, 1015 + { 1016 + "id": 2, 1017 + "name": "Bob", 1018 + "role": "user", 1019 + "department": "Sales", 1020 + "active": true 1021 + }, 1022 + { 1023 + "id": 3, 1024 + "name": "Charlie", 1025 + "role": "user", 1026 + "department": "Marketing", 1027 + "active": false 1028 + } 1029 + ], 1030 + "pagination": { 1031 + "page": 1, 1032 + "per_page": 10, 1033 + "total": 3, 1034 + "total_pages": 1 1035 + } 1036 + }, 1037 + "timestamp": "2023-11-18T21:45:30Z" 1038 + }` 1039 + 1040 + freeze.SnapJSON(t, "SnapJSON Complex API Response", jsonStr) 1041 + } 1042 + 1043 + // TestSnapJSONWithNulls tests SnapJSON handling of null values 1044 + func TestSnapJSONWithNulls(t *testing.T) { 1045 + jsonStr := `{ 1046 + "id": 1, 1047 + "name": "Item", 1048 + "description": null, 1049 + "category": null, 1050 + "tags": null, 1051 + "metadata": { 1052 + "created": "2023-01-01", 1053 + "updated": null, 1054 + "deleted": null 1055 + } 1056 + }` 1057 + 1058 + freeze.SnapJSON(t, "SnapJSON With Nulls", jsonStr) 1059 + } 1060 + 1061 + // TestSnapJSONArrayOfObjects tests SnapJSON with arrays of objects 1062 + func TestSnapJSONArrayOfObjects(t *testing.T) { 1063 + jsonStr := `[ 1064 + { 1065 + "id": 1, 1066 + "type": "post", 1067 + "title": "First Post", 1068 + "views": 150, 1069 + "likes": 42 1070 + }, 1071 + { 1072 + "id": 2, 1073 + "type": "post", 1074 + "title": "Second Post", 1075 + "views": 280, 1076 + "likes": 75 1077 + }, 1078 + { 1079 + "id": 3, 1080 + "type": "post", 1081 + "title": "Third Post", 1082 + "views": 450, 1083 + "likes": 120 1084 + } 1085 + ]` 1086 + 1087 + freeze.SnapJSON(t, "SnapJSON Array of Objects", jsonStr) 1088 + } 1089 + 1090 + // TestSnapJSONLargeNestedStructure tests SnapJSON with deeply nested JSON 1091 + func TestSnapJSONLargeNestedStructure(t *testing.T) { 1092 + jsonStr := `{ 1093 + "organization": { 1094 + "name": "TechCorp", 1095 + "id": "org_123", 1096 + "departments": [ 1097 + { 1098 + "name": "Engineering", 1099 + "manager": "Alice", 1100 + "teams": [ 1101 + { 1102 + "name": "Backend", 1103 + "lead": "John", 1104 + "members": [ 1105 + {"id": 1, "name": "John", "level": "senior"}, 1106 + {"id": 2, "name": "Jane", "level": "mid"} 1107 + ], 1108 + "projects": [ 1109 + {"id": "proj_1", "name": "API Service", "status": "active"}, 1110 + {"id": "proj_2", "name": "Database Optimization", "status": "planning"} 1111 + ] 1112 + }, 1113 + { 1114 + "name": "Frontend", 1115 + "lead": "Bob", 1116 + "members": [ 1117 + {"id": 3, "name": "Bob", "level": "senior"}, 1118 + {"id": 4, "name": "Carol", "level": "junior"} 1119 + ], 1120 + "projects": [ 1121 + {"id": "proj_3", "name": "Web App", "status": "active"} 1122 + ] 1123 + } 1124 + ] 1125 + }, 1126 + { 1127 + "name": "Sales", 1128 + "manager": "Charlie", 1129 + "teams": [ 1130 + { 1131 + "name": "Enterprise", 1132 + "lead": "Dave", 1133 + "members": [ 1134 + {"id": 5, "name": "Dave", "level": "senior"}, 1135 + {"id": 6, "name": "Eve", "level": "mid"} 1136 + ], 1137 + "projects": [] 1138 + } 1139 + ] 1140 + } 1141 + ], 1142 + "metadata": { 1143 + "founded": "2020", 1144 + "employees": 150, 1145 + "locations": ["USA", "EU", "APAC"] 1146 + } 1147 + } 1148 + }` 1149 + 1150 + freeze.SnapJSON(t, "SnapJSON Large Nested Structure", jsonStr) 1151 + } 1152 + 1153 + // TestSnapJSONWithNumbers tests SnapJSON with various number formats 1154 + func TestSnapJSONWithNumbers(t *testing.T) { 1155 + jsonStr := `{ 1156 + "integers": [0, 1, -1, 42, -100, 9999999], 1157 + "floats": [0.0, 3.14, -2.5, 0.001, 1.23e-4, 5.67e10], 1158 + "financial": { 1159 + "revenue": 1000000.50, 1160 + "expenses": 750000.75, 1161 + "profit_margin": 0.2499 1162 + }, 1163 + "measurements": { 1164 + "temperature": -40.5, 1165 + "distance": 1000.25, 1166 + "weight": 0.5 1167 + } 1168 + }` 1169 + 1170 + freeze.SnapJSON(t, "SnapJSON With Numbers", jsonStr) 1171 + } 1172 + 1173 + // TestSnapJSONWithSpecialCharacters tests SnapJSON with special chars 1174 + func TestSnapJSONWithSpecialCharacters(t *testing.T) { 1175 + jsonStr := `{ 1176 + "special": "!@#$%^&*()_+-=[]{}|;:',.<>?/", 1177 + "escaped": "line1\nline2\ttab\rcarriage", 1178 + "quotes": "He said \"hello\" and she said 'goodbye'", 1179 + "unicode": "Hello 世界 🌍 مرحبا Привет", 1180 + "paths": "C:\\Users\\name\\Documents\\file.txt", 1181 + "html": "<div class=\"container\">Content</div>", 1182 + "regex": "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$" 1183 + }` 1184 + 1185 + freeze.SnapJSON(t, "SnapJSON With Special Characters", jsonStr) 1186 + } 1187 + 1188 + // TestSnapJSONEmptyStructures tests SnapJSON with empty collections 1189 + func TestSnapJSONEmptyStructures(t *testing.T) { 1190 + jsonStr := `{ 1191 + "empty_array": [], 1192 + "empty_object": {}, 1193 + "empty_string": "", 1194 + "zero": 0, 1195 + "false_value": false, 1196 + "null_value": null, 1197 + "nested": { 1198 + "empty": [], 1199 + "also_empty": {} 1200 + } 1201 + }` 1202 + 1203 + freeze.SnapJSON(t, "SnapJSON Empty Structures", jsonStr) 1204 + } 1205 + 1206 + // TestSnapJSONMixedTypes tests SnapJSON with mixed array types 1207 + func TestSnapJSONMixedTypes(t *testing.T) { 1208 + jsonStr := `{ 1209 + "mixed_array": [ 1210 + "string", 1211 + 123, 1212 + 45.67, 1213 + true, 1214 + false, 1215 + null, 1216 + {"nested": "object"}, 1217 + [1, 2, 3] 1218 + ], 1219 + "complex": [ 1220 + {"type": "user", "id": 1}, 1221 + {"type": "post", "id": 100}, 1222 + [1, 2, 3], 1223 + "string", 1224 + null 1225 + ] 1226 + }` 1227 + 1228 + freeze.SnapJSON(t, "SnapJSON Mixed Types", jsonStr) 1229 + } 1230 + 1231 + // TestSnapJSONRealWorldExample tests SnapJSON with real-world API data 1232 + func TestSnapJSONRealWorldExample(t *testing.T) { 1233 + jsonStr := `{ 1234 + "success": true, 1235 + "data": { 1236 + "product": { 1237 + "id": "prod_12345", 1238 + "name": "Premium Wireless Headphones", 1239 + "sku": "PWH-001", 1240 + "description": "High-quality wireless headphones with noise cancellation", 1241 + "price": { 1242 + "amount": 199.99, 1243 + "currency": "USD", 1244 + "discount": 10, 1245 + "final_price": 179.99 1246 + }, 1247 + "inventory": { 1248 + "total": 500, 1249 + "available": 425, 1250 + "reserved": 50, 1251 + "damaged": 25 1252 + }, 1253 + "specifications": { 1254 + "battery_life": "30 hours", 1255 + "weight": "250g", 1256 + "colors": ["black", "white", "blue"], 1257 + "warranty_months": 24 1258 + }, 1259 + "ratings": { 1260 + "average": 4.5, 1261 + "count": 1250, 1262 + "breakdown": { 1263 + "5": 750, 1264 + "4": 350, 1265 + "3": 100, 1266 + "2": 30, 1267 + "1": 20 1268 + } 1269 + }, 1270 + "reviews": [ 1271 + { 1272 + "id": "rev_001", 1273 + "user": "john_doe", 1274 + "rating": 5, 1275 + "title": "Excellent product!", 1276 + "content": "Great sound quality and comfortable to wear.", 1277 + "helpful": 25, 1278 + "created_at": "2023-11-15T10:30:00Z" 1279 + }, 1280 + { 1281 + "id": "rev_002", 1282 + "user": "jane_smith", 1283 + "rating": 4, 1284 + "title": "Good but pricey", 1285 + "content": "Works well, could be cheaper.", 1286 + "helpful": 12, 1287 + "created_at": "2023-11-10T14:20:00Z" 1288 + } 1289 + ] 1290 + }, 1291 + "related_products": [ 1292 + { 1293 + "id": "prod_12346", 1294 + "name": "Headphone Case", 1295 + "price": 29.99 1296 + }, 1297 + { 1298 + "id": "prod_12347", 1299 + "name": "Audio Cable", 1300 + "price": 14.99 1301 + } 1302 + ] 1303 + }, 1304 + "request_id": "req_abc123def456", 1305 + "timestamp": "2023-11-18T22:00:00Z" 1306 + }` 1307 + 1308 + freeze.SnapJSON(t, "SnapJSON Real World Example", jsonStr) 1309 + } 1310 + 1311 + // ============================================================================ 945 1312 // HELPER FUNCTIONS 946 1313 // ============================================================================ 947 1314