import 'package:test/test.dart'; import 'package:xp_nix/src/config/hyprland_config_parser.dart'; void main() { group('HyprlandConfigParser', () { group('parseConfig', () { test('should parse basic config with decoration and general sections', () { const config = ''' # Basic Hyprland config exec-once = waybar \$mod = SUPER decoration { rounding = 10 blur { enabled = true passes = 2 size = 8 } shadow { enabled = true range = 15 render_power = 3 } } general { border_size = 2 col.active_border = rgba(7e5fddff) col.inactive_border = rgba(595959aa) gaps_in = 5 gaps_out = 10 } animation=windows, 1, 7, default animation=fade, 1, 4, default bind = \$mod, Q, killactive '''; final result = HyprlandConfigParser.parseConfig(config); expect(result.baseConfig, contains('exec-once = waybar')); expect(result.baseConfig, contains('\$mod = SUPER')); expect(result.baseConfig, contains('bind = \$mod, Q, killactive')); expect(result.baseConfig, isNot(contains('decoration {'))); expect(result.baseConfig, isNot(contains('general {'))); expect(result.baseConfig, isNot(contains('animation='))); expect(result.dynamicSections, hasLength(2)); expect(result.dynamicSections['decoration'], contains('rounding = 10')); expect(result.dynamicSections['decoration'], contains('blur {')); expect(result.dynamicSections['general'], contains('border_size = 2')); expect(result.animations, hasLength(2)); expect(result.animations[0], equals('animation=windows, 1, 7, default')); expect(result.animations[1], equals('animation=fade, 1, 4, default')); }); test('should handle nested sections within decoration', () { const config = ''' decoration { rounding = 15 blur { enabled = true passes = 3 size = 12 brightness = 1.1 contrast = 1.2 noise = 0.02 vibrancy = 0.3 vibrancy_darkness = 0.2 } shadow { enabled = true range = 20 render_power = 4 color = rgba(7e5fddaa) offset = 0 0 } dim_inactive = true dim_strength = 0.15 inactive_opacity = 0.85 active_opacity = 1.0 drop_shadow = true } '''; final result = HyprlandConfigParser.parseConfig(config); expect(result.dynamicSections['decoration'], contains('blur {')); expect(result.dynamicSections['decoration'], contains('enabled = true')); expect(result.dynamicSections['decoration'], contains('passes = 3')); expect(result.dynamicSections['decoration'], contains('vibrancy_darkness = 0.2')); expect(result.dynamicSections['decoration'], contains('shadow {')); expect(result.dynamicSections['decoration'], contains('color = rgba(7e5fddaa)')); expect(result.dynamicSections['decoration'], contains('dim_inactive = true')); }); test('should handle complex general section with gradient borders', () { const config = ''' general { border_size = 4 col.active_border = rgba(7e5fddff) rgba(ff5100ff) rgba(00ff88ff) 45deg col.inactive_border = rgba(595959aa) gaps_in = 4 gaps_out = 8 resize_on_border = true extend_border_grab_area = 15 allow_tearing = false layout = dwindle } '''; final result = HyprlandConfigParser.parseConfig(config); expect(result.dynamicSections['general'], contains('border_size = 4')); expect(result.dynamicSections['general'], contains('rgba(7e5fddff) rgba(ff5100ff) rgba(00ff88ff) 45deg')); expect(result.dynamicSections['general'], contains('resize_on_border = true')); expect(result.dynamicSections['general'], contains('extend_border_grab_area = 15')); }); test('should preserve non-dynamic sections in base config', () { const config = ''' input { kb_layout = us follow_mouse = 1 touchpad { natural_scroll = true } } misc { disable_hyprland_logo = true force_default_wallpaper = 0 } decoration { rounding = 5 } windowrule = float, ^(pavucontrol)\$ windowrule = workspace 2, ^(firefox)\$ '''; final result = HyprlandConfigParser.parseConfig(config); expect(result.baseConfig, contains('input {')); expect(result.baseConfig, contains('kb_layout = us')); expect(result.baseConfig, contains('touchpad {')); expect(result.baseConfig, contains('misc {')); expect(result.baseConfig, contains('windowrule = float')); expect(result.baseConfig, isNot(contains('decoration {'))); expect(result.dynamicSections['decoration'], contains('rounding = 5')); }); test('should handle config with comments and empty lines', () { const config = ''' # This is a comment exec-once = waybar # Decoration settings decoration { # Rounded corners rounding = 10 # Blur settings blur { enabled = true passes = 2 } } # General settings general { border_size = 2 # Active border color col.active_border = rgba(7e5fddff) } # Animation settings animation=windows, 1, 7, default '''; final result = HyprlandConfigParser.parseConfig(config); expect(result.baseConfig, contains('# This is a comment')); expect(result.baseConfig, contains('exec-once = waybar')); expect(result.dynamicSections['decoration'], contains('# Rounded corners')); expect(result.dynamicSections['decoration'], contains('# Blur settings')); expect(result.dynamicSections['general'], contains('# Active border color')); expect(result.animations[0], equals('animation=windows, 1, 7, default')); }); test('should handle config with no dynamic sections', () { const config = ''' exec-once = waybar \$mod = SUPER input { kb_layout = us } bind = \$mod, Q, killactive animation=windows, 1, 7, default '''; final result = HyprlandConfigParser.parseConfig(config); expect(result.baseConfig, contains('exec-once = waybar')); expect(result.baseConfig, contains('input {')); expect(result.baseConfig, contains('bind = \$mod, Q, killactive')); expect(result.dynamicSections, isEmpty); expect(result.animations, hasLength(1)); }); test('should handle inline animations in base config', () { const config = ''' exec-once = waybar animation=windows, 1, 7, default animation=fade, 1, 4, default decoration { rounding = 10 } animation=workspaces, 1, 6, default '''; final result = HyprlandConfigParser.parseConfig(config); expect(result.animations, hasLength(3)); expect(result.animations, contains('animation=windows, 1, 7, default')); expect(result.animations, contains('animation=fade, 1, 4, default')); expect(result.animations, contains('animation=workspaces, 1, 6, default')); expect(result.baseConfig, isNot(contains('animation='))); }); }); group('validateConfig', () { test('should validate complete config as valid', () { const config = ''' decoration { rounding = 10 blur { enabled = true passes = 2 size = 8 } shadow { enabled = true range = 15 render_power = 3 } } general { border_size = 2 col.active_border = rgba(7e5fddff) col.inactive_border = rgba(595959aa) gaps_in = 5 gaps_out = 10 } animation=windows, 1, 7, default '''; final result = HyprlandConfigParser.validateConfig(config); expect(result.isValid, isTrue); expect(result.issues, isEmpty); expect(result.foundSections, containsAll(['decoration', 'general'])); expect(result.hasAnimations, isTrue); }); test('should identify missing decoration properties', () { const config = ''' decoration { rounding = 10 # Missing blur and shadow sections } general { border_size = 2 col.active_border = rgba(7e5fddff) col.inactive_border = rgba(595959aa) gaps_in = 5 gaps_out = 10 } '''; final result = HyprlandConfigParser.validateConfig(config); expect(result.isValid, isFalse); expect(result.issues, contains('Missing decoration property: blur')); expect(result.issues, contains('Missing decoration property: shadow')); expect(result.issues, contains('No animation definitions found')); }); test('should identify missing blur sub-properties', () { const config = ''' decoration { rounding = 10 blur { enabled = true # Missing passes and size } shadow { enabled = true range = 15 render_power = 3 } } '''; final result = HyprlandConfigParser.validateConfig(config); expect(result.isValid, isFalse); expect(result.issues, contains('Missing blur property: passes')); expect(result.issues, contains('Missing blur property: size')); }); test('should identify missing general properties', () { const config = ''' general { border_size = 2 # Missing color and gap properties } '''; final result = HyprlandConfigParser.validateConfig(config); expect(result.isValid, isFalse); expect(result.issues, contains('Missing general property: col.active_border')); expect(result.issues, contains('Missing general property: col.inactive_border')); expect(result.issues, contains('Missing general property: gaps_in')); expect(result.issues, contains('Missing general property: gaps_out')); }); test('should accept inline animations in base config', () { const config = ''' exec-once = waybar animation=windows, 1, 7, default decoration { rounding = 10 blur { enabled = true passes = 2 size = 8 } shadow { enabled = true range = 15 render_power = 3 } } general { border_size = 2 col.active_border = rgba(7e5fddff) col.inactive_border = rgba(595959aa) gaps_in = 5 gaps_out = 10 } '''; final result = HyprlandConfigParser.validateConfig(config); expect(result.isValid, isTrue); expect(result.hasAnimations, isTrue); }); }); group('extractStylingProperties', () { test('should extract all styling properties from complete config', () { const config = ''' decoration { rounding = 15 blur { enabled = true passes = 3 size = 12 brightness = 1.1 } shadow { enabled = true range = 20 render_power = 4 } dim_inactive = true active_opacity = 1.0 } general { border_size = 4 col.active_border = rgba(7e5fddff) rgba(ff5100ff) 45deg col.inactive_border = rgba(595959aa) gaps_in = 4 gaps_out = 8 resize_on_border = true } animation=windows, 1, 8, easeout, slide animation=fade, 1, 7, easeout '''; final styling = HyprlandConfigParser.extractStylingProperties(config); expect(styling['decoration'], isA>()); expect(styling['general'], isA>()); expect(styling['animations'], isA>()); final decoration = styling['decoration'] as Map; expect(decoration['rounding'], equals('15')); expect(decoration['dim_inactive'], equals('true')); expect(decoration['active_opacity'], equals('1.0')); final general = styling['general'] as Map; expect(general['border_size'], equals('4')); expect(general['col.active_border'], equals('rgba(7e5fddff) rgba(ff5100ff) 45deg')); expect(general['resize_on_border'], equals('true')); final animations = styling['animations'] as List; expect(animations, hasLength(2)); expect(animations, contains('animation=windows, 1, 8, easeout, slide')); expect(animations, contains('animation=fade, 1, 7, easeout')); }); test('should handle nested properties correctly', () { const config = ''' decoration { blur { enabled = true passes = 2 size = 8 brightness = 1.05 contrast = 1.1 noise = 0.01 vibrancy = 0.2 } shadow { enabled = true range = 15 render_power = 3 color = rgba(7e5fdd88) offset = 0 0 } } '''; final styling = HyprlandConfigParser.extractStylingProperties(config); final decoration = styling['decoration'] as Map; // Note: The current implementation extracts nested properties as flat key-value pairs // This is a limitation but matches the current parsing behavior expect(decoration['enabled'], equals('true')); expect(decoration['passes'], equals('2')); expect(decoration['brightness'], equals('1.05')); expect(decoration['vibrancy'], equals('0.2')); expect(decoration['color'], equals('rgba(7e5fdd88)')); expect(decoration['offset'], equals('0 0')); }); test('should extract inline animations from base config', () { const config = ''' exec-once = waybar animation=windows, 1, 7, default bind = \$mod, Q, killactive animation=fade, 1, 4, default decoration { rounding = 10 } '''; final styling = HyprlandConfigParser.extractStylingProperties(config); expect(styling['animations'], isA>()); final animations = styling['animations'] as List; expect(animations, hasLength(2)); expect(animations, contains('animation=windows, 1, 7, default')); expect(animations, contains('animation=fade, 1, 4, default')); }); test('should handle config with only some styling sections', () { const config = ''' decoration { rounding = 8 blur { enabled = false } } # No general section animation=workspaces, 1, 6, default '''; final styling = HyprlandConfigParser.extractStylingProperties(config); expect(styling['decoration'], isA>()); expect(styling['general'], isNull); expect(styling['animations'], isA>()); final decoration = styling['decoration'] as Map; expect(decoration['rounding'], equals('8')); expect(decoration['enabled'], equals('false')); }); }); group('buildFullConfig', () { test('should reconstruct config with dynamic sections', () { const baseConfig = ''' exec-once = waybar \$mod = SUPER bind = \$mod, Q, killactive '''; const decorationConfig = ''' decoration { rounding = 10 blur { enabled = true } } '''; const generalConfig = ''' general { border_size = 2 gaps_in = 5 } '''; const animationConfig = ''' animation=windows, 1, 7, default animation=fade, 1, 4, default '''; final config = HyprlandConfig(baseConfig: baseConfig, dynamicSections: {}, animations: []); final fullConfig = config.buildFullConfig( decorationConfig: decorationConfig, generalConfig: generalConfig, animationConfig: animationConfig, ); expect(fullConfig, contains('exec-once = waybar')); expect(fullConfig, contains('\$mod = SUPER')); expect(fullConfig, contains('decoration {')); expect(fullConfig, contains('rounding = 10')); expect(fullConfig, contains('general {')); expect(fullConfig, contains('border_size = 2')); expect(fullConfig, contains('animation=windows, 1, 7, default')); expect(fullConfig, contains('animation=fade, 1, 4, default')); }); }); group('edge cases', () { test('should handle empty config', () { const config = ''; final result = HyprlandConfigParser.parseConfig(config); expect(result.baseConfig, isEmpty); expect(result.dynamicSections, isEmpty); expect(result.animations, isEmpty); }); test('should handle config with only comments', () { const config = ''' # This is a comment # Another comment '''; final result = HyprlandConfigParser.parseConfig(config); expect(result.baseConfig, contains('# This is a comment')); expect(result.dynamicSections, isEmpty); expect(result.animations, isEmpty); }); test('should handle malformed sections gracefully', () { const config = ''' decoration { rounding = 10 # Missing closing brace general { border_size = 2 } '''; final result = HyprlandConfigParser.parseConfig(config); // Should still parse what it can expect(result.dynamicSections['general'], contains('border_size = 2')); }); test('should handle properties with equals signs in values', () { const config = ''' general { col.active_border = rgba(7e5fddff) rgba(ff5100ff) rgba(00ff88ff) 45deg custom_prop = value=with=equals } '''; final styling = HyprlandConfigParser.extractStylingProperties(config); final general = styling['general'] as Map; expect(general['col.active_border'], equals('rgba(7e5fddff) rgba(ff5100ff) rgba(00ff88ff) 45deg')); expect(general['custom_prop'], equals('value=with=equals')); }); }); }); }