[UNTESTED] Fixes notifiers and adds some additional methods. Adds tests.

Changes:
// ArcaneEnvironment
breaking: context.read<ArcaneEnvironment>() -> ArcaneEnvironment.of(context)
breaking: context.read<ArcaneEnvironment>().state -> ArcaneEnvironment.of(context).environment;

// Feature flag service
added: reset()

// Logging service
added: registerInterface()
added: unregisterInterfaces()
added: unregisterAllInterfaces()

// ArcaneReactiveTheme
fixed: currentMode, dark, light now actually emit new values when changed
added: getters for lightTheme, darkTheme, and systemTheme
TODO: test systemTheme

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2025-04-04 17:08:06 +02:00
parent b129639f1f
commit ac82e93b9d
16 changed files with 1233 additions and 89 deletions
@@ -0,0 +1,203 @@
import "package:arcane_framework/arcane_framework.dart";
import "package:flutter_test/flutter_test.dart";
import "package:mockito/annotations.dart";
import "package:mockito/mockito.dart";
import "logging_service_test.mocks.dart";
class MyOtherLoggingInterface extends Mock implements MockLoggingInterface {}
@GenerateNiceMocks([
MockSpec<LoggingInterface>(
onMissingStub: OnMissingStub.returnDefault,
),
])
void main() {
final LoggingInterface myInterface = MockLoggingInterface();
setUp(() {
Arcane.logger.reset();
});
group("ArcaneLogger", () {
group("interface management", () {
test("registerInterfaces adds interfaces correctly", () async {
await Arcane.logger.registerInterface(myInterface);
expect(
Arcane.logger.interfaces,
contains(isA<LoggingInterface>()),
);
});
test("registering an interface doesn't initialize it", () async {
await Arcane.logger.registerInterface(myInterface);
expect(Arcane.logger.interfaces.first, isA<LoggingInterface>());
expect(myInterface.initialized, false);
verifyNever(myInterface.init());
});
test("registering an interface initializes the logger", () async {
expect(Arcane.logger.initialized, false);
await Arcane.logger.registerInterface(myInterface);
expect(Arcane.logger.initialized, true);
});
test("interfaces can be initialized through the logger", () async {
await Arcane.logger.registerInterface(myInterface);
expect(Arcane.logger.interfaces.first.initialized, false);
await Arcane.logger.initializeInterfaces();
verify(Arcane.logger.interfaces.first.init()).called(1);
});
test("multiple interfaces can be registered", () async {
await Arcane.logger.registerInterfaces([
MockLoggingInterface(),
MyOtherLoggingInterface(),
]);
expect(
Arcane.logger.interfaces,
contains(isA<MockLoggingInterface>()),
);
expect(
Arcane.logger.interfaces,
contains(isA<MyOtherLoggingInterface>()),
);
});
});
group("persistent metadata", () {
test("addPersistentMetadata adds metadata correctly", () {
Arcane.logger.addPersistentMetadata({"test": "value"});
expect(Arcane.logger.additionalMetadata["test"], equals("value"));
});
test("removePersistentMetadata removes specific key", () {
Arcane.logger.addPersistentMetadata({"test": "value", "keep": "this"});
Arcane.logger.removePersistentMetadata("test");
expect(Arcane.logger.additionalMetadata.containsKey("test"), false);
expect(Arcane.logger.additionalMetadata["keep"], equals("this"));
});
test("clearPersistentMetadata removes all metadata", () {
Arcane.logger
.addPersistentMetadata({"test": "value", "another": "value"});
Arcane.logger.clearPersistentMetadata();
expect(Arcane.logger.additionalMetadata.isEmpty, true);
});
});
group("logging messages", () {
const String logMessage = "Test";
setUp(() async {
await Arcane.logger.registerInterface(myInterface);
});
test("logging a basic message works", () async {
Arcane.log(logMessage);
verify(
myInterface.log(
logMessage,
metadata: anyNamed("metadata"),
level: anyNamed("level"),
stackTrace: anyNamed("stackTrace"),
extra: anyNamed("extra"),
),
).called(1);
});
test("logging at a different level works", () async {
Arcane.log(
logMessage,
level: Level.info,
);
verify(
myInterface.log(
logMessage,
metadata: anyNamed("metadata"),
level: Level.info,
stackTrace: anyNamed("stackTrace"),
extra: anyNamed("extra"),
),
).called(1);
Arcane.log(
logMessage,
level: Level.warning,
);
verify(
myInterface.log(
logMessage,
metadata: anyNamed("metadata"),
level: Level.warning,
stackTrace: anyNamed("stackTrace"),
extra: anyNamed("extra"),
),
).called(1);
});
test("logging a stacktrace works", () async {
final stackTrace = StackTrace.current;
Arcane.log(logMessage, stackTrace: stackTrace);
verify(
myInterface.log(
logMessage,
metadata: anyNamed("metadata"),
level: anyNamed("level"),
stackTrace: stackTrace,
extra: anyNamed("extra"),
),
).called(1);
});
test("logging an extra object works", () async {
const bool extraObject = true;
Arcane.log(
logMessage,
extra: extraObject,
);
verify(
myInterface.log(
logMessage,
metadata: anyNamed("metadata"),
level: anyNamed("level"),
stackTrace: anyNamed("stackTrace"),
extra: extraObject,
),
).called(1);
});
test("logging metadata works", () async {
final Map<String, String> metadata = {"test": "value"};
Arcane.log(
logMessage,
metadata: metadata,
);
verify(
myInterface.log(
logMessage,
metadata: metadata,
level: anyNamed("level"),
stackTrace: anyNamed("stackTrace"),
extra: anyNamed("extra"),
),
).called(1);
});
});
});
}
@@ -0,0 +1,69 @@
// Mocks generated by Mockito 5.4.5 from annotations
// in arcane_framework/test/services/logging/logging_service_test.dart.
// Do not manually edit this file.
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _i3;
import 'package:arcane_framework/src/services/logging/logging_service.dart'
as _i2;
import 'package:mockito/mockito.dart' as _i1;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
// ignore_for_file: avoid_setters_without_getters
// ignore_for_file: comment_references
// ignore_for_file: deprecated_member_use
// ignore_for_file: deprecated_member_use_from_same_package
// ignore_for_file: implementation_imports
// ignore_for_file: invalid_use_of_visible_for_testing_member
// ignore_for_file: must_be_immutable
// ignore_for_file: prefer_const_constructors
// ignore_for_file: unnecessary_parenthesis
// ignore_for_file: camel_case_types
// ignore_for_file: subtype_of_sealed_class
/// A class which mocks [LoggingInterface].
///
/// See the documentation for Mockito's code generation for more information.
class MockLoggingInterface extends _i1.Mock implements _i2.LoggingInterface {
@override
bool get initialized =>
(super.noSuchMethod(
Invocation.getter(#initialized),
returnValue: false,
returnValueForMissingStub: false,
)
as bool);
@override
_i3.Future<_i2.LoggingInterface?> init() =>
(super.noSuchMethod(
Invocation.method(#init, []),
returnValue: _i3.Future<_i2.LoggingInterface?>.value(),
returnValueForMissingStub:
_i3.Future<_i2.LoggingInterface?>.value(),
)
as _i3.Future<_i2.LoggingInterface?>);
@override
void log(
String? message, {
Map<String, dynamic>? metadata,
_i2.Level? level,
StackTrace? stackTrace,
Object? extra,
}) => super.noSuchMethod(
Invocation.method(
#log,
[message],
{
#metadata: metadata,
#level: level,
#stackTrace: stackTrace,
#extra: extra,
},
),
returnValueForMissingStub: null,
);
}