Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

@Skip(
'This file is skipped due to a cross-import that needs to be fixed. Tracked in https://github.com/flutter/flutter/issues/177028.',
)
import 'package:cupertino_ui/cupertino_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';

import '../widgets/widget_inspector_test_utils.dart';
import 'navigator_utils.dart';
import 'widget_inspector_test_utils.dart';

late List<int> selectedTabs;

Expand Down
121 changes: 121 additions & 0 deletions packages/cupertino_ui/test/widget_inspector_test_utils.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:convert';

import 'package:cupertino_ui/cupertino_ui.dart';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The import of package:cupertino_ui/cupertino_ui.dart is unused in this test utility file. To avoid unnecessary coupling and keep the test utilities clean, import package:flutter/widgets.dart instead, which provides WidgetInspectorService and WidgetsBinding.

Suggested change
import 'package:cupertino_ui/cupertino_ui.dart';
import 'package:flutter/widgets.dart';
References
  1. Unused imports should be avoided to keep the codebase clean and maintainable, adhering to Dart's standard lint rules (unused_import). (link)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it really unused?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it's definitely used.

import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';

/// Tuple-like test class for storing a [stream] and [eventKind].
///
/// Used to store the [stream] and [eventKind] that a dispatched event would be
/// sent on.
@immutable
class DispatchedEventKey {
const DispatchedEventKey({required this.stream, required this.eventKind});

final String stream;
final String eventKind;

@override
String toString() {
return '[DispatchedEventKey]($stream, $eventKind)';
}

@override
bool operator ==(Object other) {
return other is DispatchedEventKey && stream == other.stream && eventKind == other.eventKind;
}

@override
int get hashCode => Object.hash(stream, eventKind);
}

class TestWidgetInspectorService extends Object with WidgetInspectorService {
TestWidgetInspectorService() {
selection.addListener(() => selectionChangedCallback?.call());
}

final Map<String, ServiceExtensionCallback> extensions = <String, ServiceExtensionCallback>{};

final Map<DispatchedEventKey, List<Map<Object, Object?>>> eventsDispatched =
<DispatchedEventKey, List<Map<Object, Object?>>>{};

final List<Object?> objectsInspected = <Object?>[];

@override
void registerServiceExtension({
required String name,
required ServiceExtensionCallback callback,
required RegisterServiceExtensionCallback registerExtension,
}) {
assert(!extensions.containsKey(name));
extensions[name] = callback;
}

@override
void postEvent(String eventKind, Map<Object, Object?> eventData, {String stream = 'Extension'}) {
dispatchedEvents(eventKind, stream: stream).add(eventData);
}

@override
void inspect(Object? object) {
objectsInspected.add(object);
}

List<Map<Object, Object?>> dispatchedEvents(String eventKind, {String stream = 'Extension'}) {
return eventsDispatched.putIfAbsent(
DispatchedEventKey(stream: stream, eventKind: eventKind),
() => <Map<Object, Object?>>[],
);
}

List<Object?> inspectedObjects() {
return objectsInspected;
}

Iterable<Map<Object, Object?>> getServiceExtensionStateChangedEvents(String extensionName) {
return dispatchedEvents(
'Flutter.ServiceExtensionStateChanged',
).where((Map<Object, Object?> event) => event['extension'] == extensionName);
}

Future<Object?> testExtension(String name, Map<String, String> arguments) async {
expect(extensions, contains(name));
// Encode and decode to JSON to match behavior using a real service
// extension where only JSON is allowed.
return (json.decode(json.encode(await extensions[name]!(arguments)))
as Map<String, dynamic>)['result'];
}

Future<String> testBoolExtension(String name, Map<String, String> arguments) async {
expect(extensions, contains(name));
// Encode and decode to JSON to match behavior using a real service
// extension where only JSON is allowed.
return (json.decode(json.encode(await extensions[name]!(arguments)))
as Map<String, dynamic>)['enabled']
.toString();
}
Comment on lines +93 to +100

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The testBoolExtension method casts the 'enabled' value directly to String using as String. However, some service extensions return actual boolean values (e.g., true or false) rather than string representations. To make this utility more robust and prevent runtime TypeErrors, convert the value to a string using .toString().

  Future<String> testBoolExtension(String name, Map<String, String> arguments) async {
    expect(extensions, contains(name));
    // Encode and decode to JSON to match behavior using a real service
    // extension where only JSON is allowed.
    final Map<String, dynamic> result = json.decode(json.encode(await extensions[name]!(arguments))) as Map<String, dynamic>;
    return result['enabled']?.toString() ?? 'false';
  }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds reasonable, though I know you just copied this code, so it's fine either way.


int rebuildCount = 0;

@override
Future<void> forceRebuild() async {
rebuildCount++;
final WidgetsBinding binding = WidgetsBinding.instance;

if (binding.rootElement != null) {
binding.buildOwner!.reassemble(binding.rootElement!);
}
}

@override
void resetAllState() {
super.resetAllState();
eventsDispatched.clear();
objectsInspected.clear();
rebuildCount = 0;
}
}
Loading