Skip to content

Commit

Permalink
Create StubDevToolsExtensions class for use in debug and in tests (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
kenzieschmoll authored Apr 22, 2024
1 parent 3d89cb3 commit 7fb09d5
Show file tree
Hide file tree
Showing 12 changed files with 285 additions and 85 deletions.
2 changes: 1 addition & 1 deletion packages/devtools_app/benchmark/web_bundle_size_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import 'package:path/path.dart' as path;
import 'package:test/test.dart';

// Benchmark size in kB.
const int bundleSizeBenchmark = 4800;
const int bundleSizeBenchmark = 5000;
const int gzipBundleSizeBenchmark = 1450;

void main() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,16 @@ void main() {
await pumpAndConnectDevTools(tester, testApp);
resetDevToolsExtensionEnabledStates();

expect(extensionService.availableExtensions.value.length, 3);
expect(extensionService.visibleExtensions.value.length, 3);
expect(extensionService.availableExtensions.value.length, 5);
expect(extensionService.visibleExtensions.value.length, 5);
await _verifyExtensionsSettingsMenu(
tester,
[
ExtensionEnabledState.none,
ExtensionEnabledState.none,
ExtensionEnabledState.none,
ExtensionEnabledState.none, // bar
ExtensionEnabledState.none, // baz
ExtensionEnabledState.none, // foo
ExtensionEnabledState.none, // provider
ExtensionEnabledState.none, // some_tool
],
closeMenuWhenDone: false,
);
Expand All @@ -62,16 +64,18 @@ void main() {
await _verifyExtensionsSettingsMenu(
tester,
[
ExtensionEnabledState.enabled,
ExtensionEnabledState.none,
ExtensionEnabledState.none,
ExtensionEnabledState.enabled, // bar
ExtensionEnabledState.none, // baz
ExtensionEnabledState.none, // foo
ExtensionEnabledState.none, // provider
ExtensionEnabledState.none, // some_tool
],
);

await _verifyContextMenuActions(tester);
await _verifyContextMenuActionsAndDisable(tester);

expect(extensionService.availableExtensions.value.length, 3);
expect(extensionService.visibleExtensions.value.length, 2);
expect(extensionService.availableExtensions.value.length, 5);
expect(extensionService.visibleExtensions.value.length, 4);
await _verifyExtensionTabVisibility(
tester,
extensionIndex: 0,
Expand All @@ -80,22 +84,24 @@ void main() {
await _verifyExtensionsSettingsMenu(
tester,
[
ExtensionEnabledState.disabled,
ExtensionEnabledState.none,
ExtensionEnabledState.none,
ExtensionEnabledState.disabled, // bar
ExtensionEnabledState.none, // baz
ExtensionEnabledState.none, // foo
ExtensionEnabledState.none, // provider
ExtensionEnabledState.none, // some_tool
],
);

// Foo extension. Hide immediately, then re-enable from extensions menu.
// Baz extension. Hide immediately.
await _switchToExtensionScreen(
tester,
extensionIndex: 1,
initialLoad: true,
);
await _answerEnableExtensionPrompt(tester, enable: false);

expect(extensionService.availableExtensions.value.length, 3);
expect(extensionService.visibleExtensions.value.length, 1);
expect(extensionService.availableExtensions.value.length, 5);
expect(extensionService.visibleExtensions.value.length, 3);
await _verifyExtensionTabVisibility(
tester,
extensionIndex: 1,
Expand All @@ -104,31 +110,36 @@ void main() {
await _verifyExtensionsSettingsMenu(
tester,
[
ExtensionEnabledState.disabled,
ExtensionEnabledState.disabled,
ExtensionEnabledState.none,
ExtensionEnabledState.disabled, // bar
ExtensionEnabledState.disabled, // baz
ExtensionEnabledState.none, // foo
ExtensionEnabledState.none, // provider
ExtensionEnabledState.none, // some_tool
],
);

// Re-enable Baz extension from the extensions settings menu.
logStatus('verify we can re-enable an extension from the settings menu');
await _changeExtensionSetting(tester, extensionIndex: 1, enable: true);

expect(extensionService.availableExtensions.value.length, 3);
expect(extensionService.visibleExtensions.value.length, 2);
expect(extensionService.availableExtensions.value.length, 5);
expect(extensionService.visibleExtensions.value.length, 4);
await _switchToExtensionScreen(tester, extensionIndex: 1);
expect(find.byType(EnableExtensionPrompt), findsNothing);
expect(find.byType(EmbeddedExtensionView), findsOneWidget);
expect(find.byType(HtmlElementView), findsOneWidget);
await _verifyExtensionsSettingsMenu(
tester,
[
ExtensionEnabledState.disabled,
ExtensionEnabledState.enabled,
ExtensionEnabledState.none,
ExtensionEnabledState.disabled, // bar
ExtensionEnabledState.enabled, // baz
ExtensionEnabledState.none, // foo
ExtensionEnabledState.none, // provider
ExtensionEnabledState.none, // some_tool
],
);

// Provider extension. Disable directly from settings menu.
// Foo extension. Disable directly from settings menu.
logStatus(
'verify we can disable an extension screen directly from the settings menu',
);
Expand All @@ -140,8 +151,8 @@ void main() {

logStatus('disable the extension from the settings menu');
await _changeExtensionSetting(tester, extensionIndex: 2, enable: false);
expect(extensionService.availableExtensions.value.length, 3);
expect(extensionService.visibleExtensions.value.length, 1);
expect(extensionService.availableExtensions.value.length, 5);
expect(extensionService.visibleExtensions.value.length, 3);
await _verifyExtensionTabVisibility(
tester,
extensionIndex: 2,
Expand All @@ -150,9 +161,11 @@ void main() {
await _verifyExtensionsSettingsMenu(
tester,
[
ExtensionEnabledState.disabled,
ExtensionEnabledState.enabled,
ExtensionEnabledState.disabled,
ExtensionEnabledState.disabled, // bar
ExtensionEnabledState.enabled, // baz
ExtensionEnabledState.disabled, // foo
ExtensionEnabledState.none, // provider
ExtensionEnabledState.none, // some_tool
],
);
});
Expand Down Expand Up @@ -222,7 +235,7 @@ Future<void> _answerEnableExtensionPrompt(
);
}

Future<void> _verifyContextMenuActions(WidgetTester tester) async {
Future<void> _verifyContextMenuActionsAndDisable(WidgetTester tester) async {
logStatus('verify we can perform context menu actions');
final contextMenuFinder = find.descendant(
of: find.byType(EmbeddedExtensionHeader),
Expand Down Expand Up @@ -263,6 +276,7 @@ Future<void> _verifyExtensionsSettingsMenu(
.cast<DevToolsToggleButtonGroup>()
.toList();
for (int i = 0; i < toggleButtonGroups.length; i++) {
logStatus('verify extension settings toggle button states (index $i)');
final group = toggleButtonGroups[i];
final expectedStates = switch (enabledStates[i]) {
ExtensionEnabledState.enabled => [true, false],
Expand Down Expand Up @@ -316,12 +330,12 @@ Future<void> _changeExtensionSetting(
}

Future<void> _verifyExtensionVisibilitySetting(WidgetTester tester) async {
logStatus('verify we can toggle the show only enabled extensions setting');
logStatus('verify we can toggle the "show only enabled extensions" setting');
expect(
preferences.devToolsExtensions.showOnlyEnabledExtensions.value,
isFalse,
);
expect(extensionService.visibleExtensions.value.length, 3);
expect(extensionService.visibleExtensions.value.length, 5);
// No need to open the settings menu as it should already be open.
await _toggleShowOnlyEnabledExtensions(tester);
expect(
Expand All @@ -336,7 +350,7 @@ Future<void> _verifyExtensionVisibilitySetting(WidgetTester tester) async {
preferences.devToolsExtensions.showOnlyEnabledExtensions.value,
isFalse,
);
expect(extensionService.visibleExtensions.value.length, 3);
expect(extensionService.visibleExtensions.value.length, 5);

await _closeExtensionSettingsMenu(tester);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ class ExtensionSettingsDialog extends StatelessWidget {
final theme = Theme.of(context);
final availableExtensions = extensionService.availableExtensions.value;
// This dialog needs a fixed height because it contains a scrollable list.
final dialogHeight = scaleByFontFactor(300.0);
final dialogHeight =
anyTestMode ? scaleByFontFactor(1000.0) : scaleByFontFactor(300.0);
return DevToolsDialog(
title: const DialogTitleText('DevTools Extensions'),
content: SizedBox(
Expand Down
105 changes: 93 additions & 12 deletions packages/devtools_app/lib/src/shared/development_helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ final debugDevToolsExtensions =
const _debugDevToolsExtensions = false;

List<DevToolsExtensionConfig> debugHandleRefreshAvailableExtensions() =>
debugExtensions;
StubDevToolsExtensions.extensions;

ExtensionEnabledState debugHandleExtensionEnabledState({
required String extensionName,
Expand All @@ -79,10 +79,11 @@ void resetDevToolsExtensionEnabledStates() =>
/// server connection.
final stubExtensionEnabledStates = <String, ExtensionEnabledState>{};

/// Stubbed extensions so we can develop DevTools Extensions without a server
/// connection.
final List<DevToolsExtensionConfig> debugExtensions = [
DevToolsExtensionConfig.parse({
// ignore: avoid_classes_with_only_static_members, useful for testing.
abstract class StubDevToolsExtensions {
/// Extension for package:foo detected from a running app that requires a
/// connected app.
static final fooExtension = DevToolsExtensionConfig.parse({
DevToolsExtensionConfig.nameKey: 'foo',
DevToolsExtensionConfig.issueTrackerKey: 'www.google.com',
DevToolsExtensionConfig.versionKey: '1.0.0',
Expand All @@ -91,8 +92,11 @@ final List<DevToolsExtensionConfig> debugExtensions = [
DevToolsExtensionConfig.devtoolsOptionsUriKey: '/path/to/options/file',
DevToolsExtensionConfig.isPubliclyHostedKey: 'false',
DevToolsExtensionConfig.detectedFromStaticContextKey: 'false',
}),
DevToolsExtensionConfig.parse({
});

/// Extension for package:provider detected from a running app that requires a
/// connected app.
static final providerExtension = DevToolsExtensionConfig.parse({
DevToolsExtensionConfig.nameKey: 'provider',
DevToolsExtensionConfig.issueTrackerKey:
'https://github.com/rrousselGit/provider/issues',
Expand All @@ -102,9 +106,25 @@ final List<DevToolsExtensionConfig> debugExtensions = [
DevToolsExtensionConfig.devtoolsOptionsUriKey: '/path/to/options/file',
DevToolsExtensionConfig.isPubliclyHostedKey: 'true',
DevToolsExtensionConfig.detectedFromStaticContextKey: 'false',
}),
// Static extension.
DevToolsExtensionConfig.parse({
});

/// Extension for package:some_tool detected from a running app, but that does
/// not require a connected app.
static final someToolExtension = DevToolsExtensionConfig.parse({
DevToolsExtensionConfig.nameKey: 'some_tool',
DevToolsExtensionConfig.issueTrackerKey: 'www.google.com',
DevToolsExtensionConfig.versionKey: '1.0.0',
DevToolsExtensionConfig.materialIconCodePointKey: '0xe00c',
DevToolsExtensionConfig.requiresConnectionKey: 'false',
DevToolsExtensionConfig.extensionAssetsUriKey: '/path/to/some_tool',
DevToolsExtensionConfig.devtoolsOptionsUriKey: '/path/to/options/file',
DevToolsExtensionConfig.isPubliclyHostedKey: 'false',
DevToolsExtensionConfig.detectedFromStaticContextKey: 'false',
});

/// Extension for package:bar detected from a static context that does not
/// require a connected app.
static final barExtension = DevToolsExtensionConfig.parse({
DevToolsExtensionConfig.nameKey: 'bar',
DevToolsExtensionConfig.issueTrackerKey: 'www.google.com',
DevToolsExtensionConfig.versionKey: '2.0.0',
Expand All @@ -114,8 +134,69 @@ final List<DevToolsExtensionConfig> debugExtensions = [
DevToolsExtensionConfig.devtoolsOptionsUriKey: '/path/to/options/file',
DevToolsExtensionConfig.isPubliclyHostedKey: 'false',
DevToolsExtensionConfig.detectedFromStaticContextKey: 'true',
}),
];
});

// TODO(kenz): uncomment when static extensions are supported, which includes
// logic to de-duplicate extensions.
// /// Extension for package:bar detected from a static context that does not
// /// require a connected app and that is also a newer version of another static
// /// extension.
// static final newerBarExtension = DevToolsExtensionConfig.parse({
// DevToolsExtensionConfig.nameKey: 'bar',
// DevToolsExtensionConfig.issueTrackerKey: 'www.google.com',
// DevToolsExtensionConfig.versionKey: '2.1.0', // Newer version.
// DevToolsExtensionConfig.materialIconCodePointKey: 0xe638,
// DevToolsExtensionConfig.requiresConnectionKey: 'false',
// DevToolsExtensionConfig.extensionAssetsUriKey: '/path/to/bar',
// DevToolsExtensionConfig.devtoolsOptionsUriKey: '/path/to/options/file',
// DevToolsExtensionConfig.isPubliclyHostedKey: 'false',
// DevToolsExtensionConfig.detectedFromStaticContextKey: 'true',
// });

/// Extension for package:baz detected from a static context that requires a
/// connected app.
static final bazExtension = DevToolsExtensionConfig.parse({
DevToolsExtensionConfig.nameKey: 'baz',
DevToolsExtensionConfig.issueTrackerKey: 'www.google.com',
DevToolsExtensionConfig.versionKey: '1.0.0',
DevToolsExtensionConfig.materialIconCodePointKey: 0xe716,
DevToolsExtensionConfig.extensionAssetsUriKey: '/path/to/baz',
DevToolsExtensionConfig.devtoolsOptionsUriKey: '/path/to/options/file',
DevToolsExtensionConfig.isPubliclyHostedKey: 'false',
DevToolsExtensionConfig.detectedFromStaticContextKey: 'true',
});

// TODO(kenz): uncomment when static extensions are supported, which includes
// logic to de-duplicate extensions.
// /// Extension for package:foo detected from a static context that is a duplicate
// /// of a runtime extension [fooExtension].
// static final duplicateFooExtension = DevToolsExtensionConfig.parse({
// DevToolsExtensionConfig.nameKey: 'foo',
// DevToolsExtensionConfig.issueTrackerKey: 'www.google.com',
// DevToolsExtensionConfig.versionKey: '1.0.0',
// DevToolsExtensionConfig.materialIconCodePointKey: '0xe0b1',
// DevToolsExtensionConfig.extensionAssetsUriKey: '/path/to/foo',
// DevToolsExtensionConfig.devtoolsOptionsUriKey: '/path/to/options/file',
// DevToolsExtensionConfig.isPubliclyHostedKey: 'false',
// DevToolsExtensionConfig.detectedFromStaticContextKey: 'true',
// });

/// Stubbed extensions so we can develop DevTools Extensions without a server
/// connection.
static final List<DevToolsExtensionConfig> extensions = [
fooExtension,
providerExtension,
someToolExtension,
barExtension,
// TODO(kenz): uncomment when static extensions are supported, which
// includes logic to de-duplicate extensions.
// newerBarExtension,
bazExtension,
// TODO(kenz): uncomment when static extensions are supported, which
// includes logic to de-duplicate extensions.
// duplicateFooExtension,
];
}

/// Enable this flag to debug the DevTools survey logic.
///
Expand Down
3 changes: 3 additions & 0 deletions packages/devtools_app/lib/src/shared/globals.dart
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,6 @@ void setStagerMode() {
_stagerMode = true;
}
}

/// Whether DevTools is being run in any type of testing mode.
bool get anyTestMode => _integrationTestMode || _testMode || _stagerMode;
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@
// found in the LICENSE file.

import 'package:devtools_app/src/shared/analytics/constants.dart';
import 'package:devtools_app/src/shared/development_helpers.dart';
import 'package:flutter_test/flutter_test.dart';

import '../test_infra/test_data/extensions.dart';

void main() {
group('DevTools extension analytics', () {
test(
'uses extension name for public package',
() {
final public = providerExtension;
final public = StubDevToolsExtensions.providerExtension;
expect(public.isPubliclyHosted, true);
expect(public.name, 'provider');
expect(public.analyticsSafeName, 'provider');
Expand Down Expand Up @@ -51,7 +50,7 @@ void main() {
test(
'does not use extension name for private package',
() {
final private = fooExtension;
final private = StubDevToolsExtensions.fooExtension;
expect(private.isPubliclyHosted, false);
expect(private.name, 'foo');
expect(private.analyticsSafeName, 'private');
Expand Down
Loading

0 comments on commit 7fb09d5

Please sign in to comment.