From dc86fac85ee6c6d6157e3654fdb8509a07ce30fa Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Wed, 13 May 2026 08:27:04 +0200 Subject: [PATCH] test sweep: cover SettingsStore project + ext scopes + YAML emitter (T-91) Seven new tests in test/kernel/src/settings_test.dart covering the SettingsStore branches the existing 6-test suite didn't reach: - project-scoped set + get round-trip when projectDir is configured (covers the project-file readFile branch + the project-scope set path). - setting a project key without a project throws StateError. - ext.* keys default to app scope; project overrides app when both are set (covers the project-overrides-app branch in _lookup). - setProjectDir(null) clears the in-memory project values. - YAML emitter handles every scalar branch: bool, num, string (simple + special-chars + empty), list (mixed types incl null), and round-trips through reload (covers _emitScalar + _emit branches for null / List / unknown types). - load tolerates a malformed YAML file (_readFile catch path). - load returns empty when file is missing or blank. Coverage: kernel/src/settings.dart 104/123 -> 112/123 (91%). Remaining lines are a couple of corner-case formatting paths in _emit when the top-level value isn't a Map (the public API always serializes a Map, so they're effectively unreachable through normal use). Total coverage 83.85% -> 83.92%. Co-Authored-By: Claude Opus 4.7 (1M context) --- test/kernel/src/settings_test.dart | 86 ++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/test/kernel/src/settings_test.dart b/test/kernel/src/settings_test.dart index f758ca27..e8566b32 100644 --- a/test/kernel/src/settings_test.dart +++ b/test/kernel/src/settings_test.dart @@ -90,5 +90,91 @@ void main() { await store.set('app.k', 'v'); expect(count, greaterThanOrEqualTo(1)); }); + + test('project-scoped set + get round-trip when projectDir is configured', () async { + final project = await Directory.systemTemp.createTemp('clide_settings_project_'); + addTearDown(() => project.deleteSync(recursive: true)); + await store.setProjectDir(project); + await store.set('project.foo.bar', 7); + expect(store.get('project.foo.bar'), 7); + // Persisted on disk. + final file = File('${project.path}/.clide/settings.yaml'); + expect(file.existsSync(), isTrue); + // Reload sees the value. + await store.load(); + expect(store.get('project.foo.bar'), 7); + }); + + test('setting a project-scoped key without a project throws StateError', () async { + expect( + () async => store.set('project.unset', 1), + throwsA(isA()), + ); + }); + + test('ext.* keys default to app scope; project overrides app for the same key', () async { + await store.set('ext.foo.bar', 'app-value'); + expect(store.get('ext.foo.bar'), 'app-value'); + // With a project open, the project value wins. + final project = await Directory.systemTemp.createTemp('clide_settings_extp_'); + addTearDown(() => project.deleteSync(recursive: true)); + await store.setProjectDir(project); + // Inject a project-scoped ext value via the on-disk file (simulating + // a per-project override). + final pfile = File('${project.path}/.clide/settings.yaml'); + await pfile.parent.create(recursive: true); + await pfile.writeAsString('ext:\n foo:\n bar: project-value\n'); + await store.load(); + expect(store.get('ext.foo.bar'), 'project-value'); + }); + + test('setProjectDir(null) clears the project values', () async { + final project = await Directory.systemTemp.createTemp('clide_settings_clear_'); + addTearDown(() => project.deleteSync(recursive: true)); + await store.setProjectDir(project); + await store.set('project.x', 1); + await store.setProjectDir(null); + expect(store.get('project.x'), isNull); + }); + + test('YAML emitter handles every scalar / collection branch', () async { + // Null, list of mixed types, bool, num, string with special chars, + // empty string, empty map → exercises _emitScalar + _emit. + await store.set('app.bool', true); + await store.set('app.num', 42); + await store.set('app.str.simple', 'hi'); + await store.set('app.str.special', 'has:colon and # hash'); + await store.set('app.str.empty', ''); + await store.set('app.list', [1, 'two', null, false]); + // Round-trip through reload. + await store.load(); + expect(store.get('app.bool'), isTrue); + expect(store.get('app.num'), 42); + expect(store.get('app.str.simple'), 'hi'); + expect(store.get('app.str.special'), 'has:colon and # hash'); + expect(store.get('app.str.empty'), ''); + expect(store.get('app.list'), [1, 'two', null, false]); + }); + + test('load tolerates a malformed YAML file', () async { + // Write garbage to the on-disk app settings, then load. + final f = File('${tmp.path}/settings.yaml'); + await f.writeAsString(': : : not yaml'); + await store.load(); + // No exception; in-memory store is empty. + expect(store.get('app.anything'), isNull); + }); + + test('load returns empty when the settings file is blank or missing', () async { + // File missing → empty. + final f = File('${tmp.path}/settings.yaml'); + if (f.existsSync()) await f.delete(); + await store.load(); + expect(store.get('app.foo'), isNull); + // Blank file → empty. + await f.writeAsString(''); + await store.load(); + expect(store.get('app.foo'), isNull); + }); }); }