Starting to go places. More testing in place, need to solidify the dashboard api boundary next
This commit is contained in:
@@ -0,0 +1,617 @@
|
||||
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'));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user