dissolve app/ into repo root (D-056)
Single Flutter package at the repo root. All code, tests, assets, and platform directories moved from app/ to root. Package renamed from clide_app to clide — all imports rewritten. Merged pubspec combines core (ffi) and app (flutter, yaml, xterm) dependencies. Makefile simplified: no APP_PRESENT conditionals, no cd, no daemon lifecycle. 317 tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:clide/kernel/kernel.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
ThemeDefinition _def(String name, Color primary) => ThemeDefinition(
|
||||
name: name,
|
||||
displayName: name,
|
||||
dark: true,
|
||||
palette: Palette({
|
||||
'primary': primary,
|
||||
'accent': primary,
|
||||
'background': const Color(0xFF000000),
|
||||
'surface': const Color(0xFF111111),
|
||||
'panel': const Color(0xFF222222),
|
||||
'foreground': const Color(0xFFFFFFFF),
|
||||
'success': const Color(0xFF00FF00),
|
||||
'warning': const Color(0xFFFFFF00),
|
||||
'error': const Color(0xFFFF0000),
|
||||
}),
|
||||
);
|
||||
|
||||
void main() {
|
||||
group('ThemeController', () {
|
||||
test('starts on first bundled theme', () {
|
||||
final c = ThemeController(bundled: [
|
||||
_def('a', const Color(0xFF111111)),
|
||||
_def('b', const Color(0xFF222222)),
|
||||
]);
|
||||
expect(c.currentName, 'a');
|
||||
});
|
||||
|
||||
test('honors initialName when present', () {
|
||||
final c = ThemeController(
|
||||
bundled: [
|
||||
_def('a', const Color(0xFF000000)),
|
||||
_def('b', const Color(0xFF999999))
|
||||
],
|
||||
initialName: 'b',
|
||||
);
|
||||
expect(c.currentName, 'b');
|
||||
});
|
||||
|
||||
test('silently falls back to first when initialName is unknown', () {
|
||||
final c = ThemeController(
|
||||
bundled: [_def('a', const Color(0xFF000000))],
|
||||
initialName: 'missing',
|
||||
);
|
||||
expect(c.currentName, 'a');
|
||||
});
|
||||
|
||||
test('select changes current + notifies listeners', () {
|
||||
final c = ThemeController(bundled: [
|
||||
_def('a', const Color(0xFF000000)),
|
||||
_def('b', const Color(0xFF333333)),
|
||||
]);
|
||||
var count = 0;
|
||||
c.addListener(() => count++);
|
||||
c.select('b');
|
||||
expect(c.currentName, 'b');
|
||||
expect(count, 1);
|
||||
});
|
||||
|
||||
test('select on same theme is a no-op', () {
|
||||
final c = ThemeController(bundled: [_def('a', const Color(0xFF000000))]);
|
||||
var count = 0;
|
||||
c.addListener(() => count++);
|
||||
c.select('a');
|
||||
expect(count, 0);
|
||||
});
|
||||
|
||||
test('select throws on unknown name', () {
|
||||
final c = ThemeController(bundled: [_def('a', const Color(0xFF000000))]);
|
||||
expect(() => c.select('nope'), throwsA(isA<ArgumentError>()));
|
||||
});
|
||||
|
||||
test('registerTheme adds a new theme and rebuilds current if matching', () {
|
||||
final c = ThemeController(bundled: [_def('a', const Color(0xFFAAAAAA))]);
|
||||
expect(c.available.length, 1);
|
||||
c.registerTheme(_def('b', const Color(0xFFBBBBBB)));
|
||||
expect(c.available.length, 2);
|
||||
// re-register 'a' with a different palette — current rebuilds
|
||||
var count = 0;
|
||||
c.addListener(() => count++);
|
||||
c.registerTheme(_def('a', const Color(0xFF123456)));
|
||||
expect(count, 1);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:clide/kernel/kernel.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
const loader = ThemeLoader();
|
||||
|
||||
group('ThemeLoader.fromYamlString', () {
|
||||
test('parses palette-only theme', () {
|
||||
final def = loader.fromYamlString('''
|
||||
name: summer-night
|
||||
display_name: Summer Night
|
||||
dark: true
|
||||
palette:
|
||||
primary: "#00a3d2"
|
||||
background: "#21262F"
|
||||
foreground: "#E2E8F5"
|
||||
''');
|
||||
expect(def.name, 'summer-night');
|
||||
expect(def.displayName, 'Summer Night');
|
||||
expect(def.dark, true);
|
||||
expect(def.palette.lookup('primary'), const Color(0xFF00A3D2));
|
||||
expect(def.semanticOverride, isNull);
|
||||
expect(def.surfaceOverride, isNull);
|
||||
});
|
||||
|
||||
test('captures semantic overrides as palette-resolved colors', () {
|
||||
final def = loader.fromYamlString('''
|
||||
name: t
|
||||
palette:
|
||||
red: "#FF0000"
|
||||
blue: "#0000FF"
|
||||
semantic:
|
||||
mainchrome: red
|
||||
focus: "#123456"
|
||||
''');
|
||||
expect(
|
||||
def.semanticOverride!.lookup('mainchrome'), const Color(0xFFFF0000));
|
||||
expect(def.semanticOverride!.lookup('focus'), const Color(0xFF123456));
|
||||
});
|
||||
|
||||
test('captures surface overrides as ref strings', () {
|
||||
final def = loader.fromYamlString('''
|
||||
name: t
|
||||
palette:
|
||||
red: "#FF0000"
|
||||
surface:
|
||||
panel.background: "semantic.mainchrome"
|
||||
panel.border: red
|
||||
''');
|
||||
expect(def.surfaceOverride, {
|
||||
'panel.background': 'semantic.mainchrome',
|
||||
'panel.border': 'red',
|
||||
});
|
||||
});
|
||||
|
||||
test('throws when name is missing and no fallback', () {
|
||||
expect(
|
||||
() => loader.fromYamlString('palette: { fg: "#fff" }'),
|
||||
throwsA(isA<FormatException>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('throws when palette is missing', () {
|
||||
expect(
|
||||
() => loader.fromYamlString('name: t'),
|
||||
throwsA(isA<FormatException>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('fallback name is used when name is absent', () {
|
||||
final def = loader.fromYamlString(
|
||||
'palette: { fg: "#fff" }',
|
||||
fallbackName: 'inferred',
|
||||
);
|
||||
expect(def.name, 'inferred');
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:clide/kernel/kernel.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
const resolver = ThemeResolver();
|
||||
|
||||
Palette paletteOf(Map<String, Color> colors) => Palette(colors);
|
||||
|
||||
group('ThemeResolver default fallbacks', () {
|
||||
test('design-shape palette resolves every token', () {
|
||||
final tokens = resolver.resolve(
|
||||
palette: paletteOf(const {
|
||||
'bg': Color(0xFF20202C),
|
||||
'bgSunken': Color(0xFF1A1A24),
|
||||
'surface': Color(0xFF242838),
|
||||
'surfaceHi': Color(0xFF2C3046),
|
||||
'border': Color(0xFF343850),
|
||||
'borderHi': Color(0xFF3C445C),
|
||||
'textHi': Color(0xFFE6E8F2),
|
||||
'text': Color(0xFFB1BBE3),
|
||||
'textDim': Color(0xFF78809C),
|
||||
'textMute': Color(0xFF545C84),
|
||||
'accent': Color(0xFF78A0F8),
|
||||
'accentPress': Color(0xFF6C90DC),
|
||||
'accentSoft': Color(0x2178A0F8),
|
||||
'onAccent': Color(0xFF0D1020),
|
||||
'ok': Color(0xFF7DD3A8),
|
||||
'warn': Color(0xFFE6C370),
|
||||
'err': Color(0xFFE87D7D),
|
||||
'info': Color(0xFF78A0F8),
|
||||
}),
|
||||
);
|
||||
expect(tokens.globalBackground, const Color(0xFF20202C));
|
||||
expect(tokens.globalForeground, const Color(0xFFE6E8F2));
|
||||
expect(tokens.panelBackground, const Color(0xFF1A1A24));
|
||||
expect(tokens.sidebarItemSelected, const Color(0xFF2C3046));
|
||||
expect(tokens.statusSuccess, const Color(0xFF7DD3A8));
|
||||
expect(tokens.statusError, const Color(0xFFE87D7D));
|
||||
expect(tokens.globalTextMuted, const Color(0xFF78809C));
|
||||
expect(tokens.buttonBackground, const Color(0xFF78A0F8));
|
||||
expect(tokens.buttonForeground, const Color(0xFF0D1020));
|
||||
});
|
||||
|
||||
test('legacy palette keys still resolve via fallback chains', () {
|
||||
final tokens = resolver.resolve(
|
||||
palette: paletteOf(const {
|
||||
'primary': Color(0xFF00A3D2),
|
||||
'accent': Color(0xFFFA5F8B),
|
||||
'background': Color(0xFF21262F),
|
||||
'surface': Color(0xFF393E48),
|
||||
'panel': Color(0xFF292E38),
|
||||
'foreground': Color(0xFFE2E8F5),
|
||||
'muted': Color(0xFF6A7280),
|
||||
'success': Color(0xFF00AB9A),
|
||||
'warning': Color(0xFFD08447),
|
||||
'error': Color(0xFFF06C6F),
|
||||
'info': Color(0xFF00A3D2),
|
||||
}),
|
||||
);
|
||||
expect(tokens.globalBackground, const Color(0xFF21262F));
|
||||
expect(tokens.globalForeground, const Color(0xFFE2E8F5));
|
||||
expect(tokens.panelBackground, const Color(0xFF292E38));
|
||||
expect(tokens.statusSuccess, const Color(0xFF00AB9A));
|
||||
expect(tokens.statusError, const Color(0xFFF06C6F));
|
||||
});
|
||||
});
|
||||
|
||||
group('ThemeResolver overrides', () {
|
||||
test('surface override wins over default', () {
|
||||
final tokens = resolver.resolve(
|
||||
palette: paletteOf(const {
|
||||
'bg': Color(0xFF333333),
|
||||
'bgSunken': Color(0xFF222222),
|
||||
'surface': Color(0xFF444444),
|
||||
'border': Color(0xFF555555),
|
||||
'textHi': Color(0xFFFFFFFF),
|
||||
'accent': Color(0xFF111111),
|
||||
'ok': Color(0xFF008800),
|
||||
'warn': Color(0xFFFF8800),
|
||||
'err': Color(0xFFFF0000),
|
||||
'info': Color(0xFF111111),
|
||||
}),
|
||||
surfaceOverride: const {
|
||||
'panel.background': '#00FF00',
|
||||
},
|
||||
);
|
||||
expect(tokens.panelBackground, const Color(0xFF00FF00));
|
||||
});
|
||||
|
||||
test('semantic override propagates when palette key is absent', () {
|
||||
final tokens = resolver.resolve(
|
||||
palette: paletteOf(const {
|
||||
'bg': Color(0xFF333333),
|
||||
'bgSunken': Color(0xFF222222),
|
||||
'surface': Color(0xFF444444),
|
||||
'surfaceHi': Color(0xFF555555),
|
||||
'border': Color(0xFF666666),
|
||||
'textHi': Color(0xFFFFFFFF),
|
||||
'ok': Color(0xFF008800),
|
||||
'warn': Color(0xFFFF8800),
|
||||
'err': Color(0xFFFF0000),
|
||||
'info': Color(0xFF111111),
|
||||
}),
|
||||
semanticOverride: const SemanticRoles({
|
||||
'focus': Color(0xFF007777),
|
||||
}),
|
||||
);
|
||||
// No 'accent' in palette, so globalFocus falls through to
|
||||
// semantic.focus which is overridden.
|
||||
expect(tokens.globalFocus, const Color(0xFF007777));
|
||||
});
|
||||
|
||||
test('extensionOverride populates extensionTokens map', () {
|
||||
final tokens = resolver.resolve(
|
||||
palette: paletteOf(const {
|
||||
'bg': Color(0xFF333333),
|
||||
'bgSunken': Color(0xFF222222),
|
||||
'surface': Color(0xFF444444),
|
||||
'border': Color(0xFF555555),
|
||||
'textHi': Color(0xFFFFFFFF),
|
||||
'accent': Color(0xFF111111),
|
||||
}),
|
||||
extensionOverride: const {
|
||||
'ext.sqlite.table.background': '#ABCDEF',
|
||||
},
|
||||
);
|
||||
expect(tokens.extensionTokens['ext.sqlite.table.background'],
|
||||
const Color(0xFFABCDEF));
|
||||
});
|
||||
});
|
||||
|
||||
group('Palette.parseHex', () {
|
||||
test('parses 6-digit hex as opaque', () {
|
||||
expect(Palette.parseHex('#112233'), const Color(0xFF112233));
|
||||
});
|
||||
test('parses 8-digit hex with alpha', () {
|
||||
expect(Palette.parseHex('#80112233'), const Color(0x80112233));
|
||||
});
|
||||
test('returns null on invalid', () {
|
||||
expect(Palette.parseHex('nope'), isNull);
|
||||
expect(Palette.parseHex('#12'), isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user