618 lines
17 KiB
Dart
618 lines
17 KiB
Dart
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<Map<String, dynamic>>());
|
|
expect(styling['general'], isA<Map<String, dynamic>>());
|
|
expect(styling['animations'], isA<List<String>>());
|
|
|
|
final decoration = styling['decoration'] as Map<String, dynamic>;
|
|
expect(decoration['rounding'], equals('15'));
|
|
expect(decoration['dim_inactive'], equals('true'));
|
|
expect(decoration['active_opacity'], equals('1.0'));
|
|
|
|
final general = styling['general'] as Map<String, dynamic>;
|
|
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<String>;
|
|
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<String, dynamic>;
|
|
|
|
// 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<List<String>>());
|
|
final animations = styling['animations'] as List<String>;
|
|
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<Map<String, dynamic>>());
|
|
expect(styling['general'], isNull);
|
|
expect(styling['animations'], isA<List<String>>());
|
|
|
|
final decoration = styling['decoration'] as Map<String, dynamic>;
|
|
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<String, dynamic>;
|
|
|
|
expect(general['col.active_border'], equals('rgba(7e5fddff) rgba(ff5100ff) rgba(00ff88ff) 45deg'));
|
|
expect(general['custom_prop'], equals('value=with=equals'));
|
|
});
|
|
});
|
|
});
|
|
}
|