flutter/packages/flutter_tools/test/general.shard/terminal_handler_test.dart
Alex Li 771b8a4197
🔊 [tool] Add a wirelessly connected device name as displayName (#160497)
An improvement for #144634.

A wirelessly connected device will displayed as `Target device 1
(wireless)` in various of places.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
2025-01-10 20:51:34 +00:00

1469 lines
50 KiB
Dart

// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/base/dds.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/signals.dart';
import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/tools/shader_compiler.dart';
import 'package:flutter_tools/src/compile.dart';
import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/devfs.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/resident_devtools_handler.dart';
import 'package:flutter_tools/src/resident_runner.dart';
import 'package:flutter_tools/src/vmservice.dart';
import 'package:test/fake.dart';
import 'package:vm_service/vm_service.dart' as vm_service;
import '../src/common.dart';
import '../src/fake_vm_services.dart';
import '../src/fakes.dart';
final vm_service.Isolate fakeUnpausedIsolate = vm_service.Isolate(
id: '1',
pauseEvent: vm_service.Event(kind: vm_service.EventKind.kResume, timestamp: 0),
breakpoints: <vm_service.Breakpoint>[],
extensionRPCs: <String>[],
libraries: <vm_service.LibraryRef>[
vm_service.LibraryRef(id: '1', uri: 'file:///hello_world/main.dart', name: ''),
],
livePorts: 0,
name: 'test',
number: '1',
pauseOnExit: false,
runnable: true,
startTime: 0,
isSystemIsolate: false,
isolateFlags: <vm_service.IsolateFlag>[],
);
final FlutterView fakeFlutterView = FlutterView(id: 'a', uiIsolate: fakeUnpausedIsolate);
final FakeVmServiceRequest listViews = FakeVmServiceRequest(
method: kListViewsMethod,
jsonResponse: <String, Object>{
'views': <Object>[fakeFlutterView.toJson()],
},
);
void main() {
testWithoutContext('keyboard input handling single help character', () async {
final TestRunner testRunner = TestRunner();
final Logger logger = BufferLogger.test();
final Signals signals = Signals.test();
final Terminal terminal = Terminal.test();
final MemoryFileSystem fs = MemoryFileSystem.test();
final ProcessInfo processInfo = ProcessInfo.test(fs);
final TerminalHandler terminalHandler = TerminalHandler(
testRunner,
logger: logger,
signals: signals,
terminal: terminal,
processInfo: processInfo,
reportReady: false,
);
expect(testRunner.hasHelpBeenPrinted, false);
await terminalHandler.processTerminalInput('h');
expect(testRunner.hasHelpBeenPrinted, true);
});
testWithoutContext('keyboard input handling help character surrounded with newlines', () async {
final TestRunner testRunner = TestRunner();
final Logger logger = BufferLogger.test();
final Signals signals = Signals.test();
final Terminal terminal = Terminal.test();
final MemoryFileSystem fs = MemoryFileSystem.test();
final ProcessInfo processInfo = ProcessInfo.test(fs);
final TerminalHandler terminalHandler = TerminalHandler(
testRunner,
logger: logger,
signals: signals,
terminal: terminal,
processInfo: processInfo,
reportReady: false,
);
expect(testRunner.hasHelpBeenPrinted, false);
await terminalHandler.processTerminalInput('\nh\n');
expect(testRunner.hasHelpBeenPrinted, true);
});
group('keycode verification, brought to you by the letter', () {
testWithoutContext('a, can handle trailing newlines', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
supportsServiceProtocol: false,
);
await terminalHandler.processTerminalInput('a\n');
expect(terminalHandler.lastReceivedCommand, 'a');
});
testWithoutContext('n, can handle trailing only newlines', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[]);
await terminalHandler.processTerminalInput('\n\n');
expect(terminalHandler.lastReceivedCommand, '');
});
testWithoutContext('a - debugToggleProfileWidgetBuilds', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.profileWidgetBuilds',
args: <String, Object>{'isolateId': '1'},
jsonResponse: <String, Object>{'enabled': 'false'},
),
const FakeVmServiceRequest(
method: 'ext.flutter.profileWidgetBuilds',
args: <String, Object>{'isolateId': '1', 'enabled': 'true'},
jsonResponse: <String, Object>{'enabled': 'true'},
),
]);
await terminalHandler.processTerminalInput('a');
});
testWithoutContext('a - debugToggleProfileWidgetBuilds with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.profileWidgetBuilds',
args: <String, Object>{'isolateId': '1'},
jsonResponse: <String, Object>{'enabled': 'false'},
),
const FakeVmServiceRequest(
method: 'ext.flutter.profileWidgetBuilds',
args: <String, Object>{'isolateId': '1', 'enabled': 'true'},
jsonResponse: <String, Object>{'enabled': 'true'},
),
], web: true);
await terminalHandler.processTerminalInput('a');
});
testWithoutContext(
'a - debugToggleProfileWidgetBuilds without service protocol is skipped',
() async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
supportsServiceProtocol: false,
);
await terminalHandler.processTerminalInput('a');
},
);
testWithoutContext('b - debugToggleBrightness', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.brightnessOverride',
args: <String, Object>{'isolateId': '1'},
jsonResponse: <String, Object>{'value': 'Brightness.light'},
),
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.brightnessOverride',
args: <String, Object>{'isolateId': '1', 'value': 'Brightness.dark'},
jsonResponse: <String, Object>{'value': 'Brightness.dark'},
),
]);
await terminalHandler.processTerminalInput('b');
expect(terminalHandler.logger.statusText, contains('Changed brightness to Brightness.dark'));
});
testWithoutContext('b - debugToggleBrightness with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.brightnessOverride',
args: <String, Object>{'isolateId': '1'},
jsonResponse: <String, Object>{'value': 'Brightness.light'},
),
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.brightnessOverride',
args: <String, Object>{'isolateId': '1', 'value': 'Brightness.dark'},
jsonResponse: <String, Object>{'value': 'Brightness.dark'},
),
], web: true);
await terminalHandler.processTerminalInput('b');
expect(terminalHandler.logger.statusText, contains('Changed brightness to Brightness.dark'));
});
testWithoutContext('b - debugToggleBrightness without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
supportsServiceProtocol: false,
);
await terminalHandler.processTerminalInput('b');
});
testWithoutContext('d,D - detach', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[]);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await terminalHandler.processTerminalInput('d');
expect(runner.calledDetach, true);
runner.calledDetach = false;
await terminalHandler.processTerminalInput('D');
expect(runner.calledDetach, true);
});
testWithoutContext('h,H,? - printHelp', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[]);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await terminalHandler.processTerminalInput('h');
expect(runner.calledPrintWithDetails, true);
runner.calledPrintWithDetails = false;
await terminalHandler.processTerminalInput('H');
expect(runner.calledPrintWithDetails, true);
runner.calledPrintWithDetails = false;
await terminalHandler.processTerminalInput('?');
expect(runner.calledPrintWithDetails, true);
});
testWithoutContext('i - debugToggleWidgetInspector', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.inspector.show',
args: <String, Object>{'isolateId': '1'},
),
]);
await terminalHandler.processTerminalInput('i');
});
testWithoutContext('i - debugToggleWidgetInspector with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.inspector.show',
args: <String, Object>{'isolateId': '1'},
),
], web: true);
await terminalHandler.processTerminalInput('i');
});
testWithoutContext(
'i - debugToggleWidgetInspector without service protocol is skipped',
() async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
supportsServiceProtocol: false,
);
await terminalHandler.processTerminalInput('i');
},
);
testWithoutContext('I - debugToggleInvertOversizedImages', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.invertOversizedImages',
args: <String, Object>{'isolateId': '1'},
),
]);
await terminalHandler.processTerminalInput('I');
});
testWithoutContext('I - debugToggleInvertOversizedImages with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.invertOversizedImages',
args: <String, Object>{'isolateId': '1'},
),
], web: true);
await terminalHandler.processTerminalInput('I');
});
testWithoutContext(
'I - debugToggleInvertOversizedImages without service protocol is skipped',
() async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
supportsServiceProtocol: false,
);
await terminalHandler.processTerminalInput('I');
},
);
testWithoutContext('I - debugToggleInvertOversizedImages in profile mode is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
buildMode: BuildMode.profile,
);
await terminalHandler.processTerminalInput('I');
});
testWithoutContext('L - debugDumpLayerTree', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpLayerTree',
args: <String, Object>{'isolateId': '1'},
jsonResponse: <String, Object>{'data': 'LAYER TREE'},
),
]);
await terminalHandler.processTerminalInput('L');
expect(terminalHandler.logger.statusText, contains('LAYER TREE'));
});
testWithoutContext('L - debugDumpLayerTree with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpLayerTree',
args: <String, Object>{'isolateId': '1'},
jsonResponse: <String, Object>{'data': 'LAYER TREE'},
),
], web: true);
await terminalHandler.processTerminalInput('L');
expect(terminalHandler.logger.statusText, contains('LAYER TREE'));
});
testWithoutContext(
'L - debugDumpLayerTree with service protocol and profile mode is skipped',
() async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
buildMode: BuildMode.profile,
);
await terminalHandler.processTerminalInput('L');
},
);
testWithoutContext('L - debugDumpLayerTree without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
supportsServiceProtocol: false,
);
await terminalHandler.processTerminalInput('L');
});
testWithoutContext('f - debugDumpFocusTree', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpFocusTree',
args: <String, Object>{'isolateId': '1'},
jsonResponse: <String, Object>{'data': 'FOCUS TREE'},
),
]);
await terminalHandler.processTerminalInput('f');
expect(terminalHandler.logger.statusText, contains('FOCUS TREE'));
});
testWithoutContext('f - debugDumpLayerTree with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpFocusTree',
args: <String, Object>{'isolateId': '1'},
jsonResponse: <String, Object>{'data': 'FOCUS TREE'},
),
], web: true);
await terminalHandler.processTerminalInput('f');
expect(terminalHandler.logger.statusText, contains('FOCUS TREE'));
});
testWithoutContext(
'f - debugDumpFocusTree with service protocol and profile mode is skipped',
() async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
buildMode: BuildMode.profile,
);
await terminalHandler.processTerminalInput('f');
},
);
testWithoutContext('f - debugDumpFocusTree without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
supportsServiceProtocol: false,
);
await terminalHandler.processTerminalInput('f');
});
testWithoutContext('o,O - debugTogglePlatform', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
// Request 1.
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.platformOverride',
args: <String, Object>{'isolateId': '1'},
jsonResponse: <String, Object>{'value': 'iOS'},
),
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.platformOverride',
args: <String, Object>{'isolateId': '1', 'value': 'windows'},
jsonResponse: <String, Object>{'value': 'windows'},
),
// Request 2.
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.platformOverride',
args: <String, Object>{'isolateId': '1'},
jsonResponse: <String, Object>{'value': 'android'},
),
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.platformOverride',
args: <String, Object>{'isolateId': '1', 'value': 'iOS'},
jsonResponse: <String, Object>{'value': 'iOS'},
),
]);
await terminalHandler.processTerminalInput('o');
await terminalHandler.processTerminalInput('O');
expect(terminalHandler.logger.statusText, contains('Switched operating system to windows'));
expect(terminalHandler.logger.statusText, contains('Switched operating system to iOS'));
});
testWithoutContext('o,O - debugTogglePlatform with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
// Request 1.
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.platformOverride',
args: <String, Object>{'isolateId': '1'},
jsonResponse: <String, Object>{'value': 'iOS'},
),
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.platformOverride',
args: <String, Object>{'isolateId': '1', 'value': 'windows'},
jsonResponse: <String, Object>{'value': 'windows'},
),
// Request 2.
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.platformOverride',
args: <String, Object>{'isolateId': '1'},
jsonResponse: <String, Object>{'value': 'android'},
),
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.platformOverride',
args: <String, Object>{'isolateId': '1', 'value': 'iOS'},
jsonResponse: <String, Object>{'value': 'iOS'},
),
], web: true);
await terminalHandler.processTerminalInput('o');
await terminalHandler.processTerminalInput('O');
expect(terminalHandler.logger.statusText, contains('Switched operating system to windows'));
expect(terminalHandler.logger.statusText, contains('Switched operating system to iOS'));
});
testWithoutContext('o,O - debugTogglePlatform without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
supportsServiceProtocol: false,
);
await terminalHandler.processTerminalInput('o');
await terminalHandler.processTerminalInput('O');
});
testWithoutContext('p - debugToggleDebugPaintSizeEnabled', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugPaint',
args: <String, Object>{'isolateId': '1'},
),
]);
await terminalHandler.processTerminalInput('p');
});
testWithoutContext('p - debugToggleDebugPaintSizeEnabled with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugPaint',
args: <String, Object>{'isolateId': '1'},
),
], web: true);
await terminalHandler.processTerminalInput('p');
});
testWithoutContext(
'p - debugToggleDebugPaintSizeEnabled without service protocol is skipped',
() async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
supportsServiceProtocol: false,
);
await terminalHandler.processTerminalInput('p');
},
);
testWithoutContext('P - debugTogglePerformanceOverlayOverride', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.showPerformanceOverlay',
args: <String, Object>{'isolateId': '1'},
),
]);
await terminalHandler.processTerminalInput('P');
});
testWithoutContext(
'P - debugTogglePerformanceOverlayOverride with web target is skipped ',
() async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
web: true,
);
await terminalHandler.processTerminalInput('P');
},
);
testWithoutContext(
'P - debugTogglePerformanceOverlayOverride without service protocol is skipped ',
() async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
supportsServiceProtocol: false,
);
await terminalHandler.processTerminalInput('P');
},
);
testWithoutContext('S - debugDumpSemanticsTreeInTraversalOrder', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpSemanticsTreeInTraversalOrder',
args: <String, Object>{'isolateId': '1'},
jsonResponse: <String, Object>{'data': 'SEMANTICS DATA'},
),
]);
await terminalHandler.processTerminalInput('S');
expect(terminalHandler.logger.statusText, contains('SEMANTICS DATA'));
});
testWithoutContext('S - debugDumpSemanticsTreeInTraversalOrder with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpSemanticsTreeInTraversalOrder',
args: <String, Object>{'isolateId': '1'},
jsonResponse: <String, Object>{'data': 'SEMANTICS DATA'},
),
], web: true);
await terminalHandler.processTerminalInput('S');
expect(terminalHandler.logger.statusText, contains('SEMANTICS DATA'));
});
testWithoutContext(
'S - debugDumpSemanticsTreeInTraversalOrder without service protocol is skipped',
() async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
supportsServiceProtocol: false,
);
await terminalHandler.processTerminalInput('S');
},
);
testWithoutContext('U - debugDumpSemanticsTreeInInverseHitTestOrder', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder',
args: <String, Object>{'isolateId': '1'},
jsonResponse: <String, Object>{'data': 'SEMANTICS DATA'},
),
]);
await terminalHandler.processTerminalInput('U');
expect(terminalHandler.logger.statusText, contains('SEMANTICS DATA'));
});
testWithoutContext('U - debugDumpSemanticsTreeInInverseHitTestOrder with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder',
args: <String, Object>{'isolateId': '1'},
jsonResponse: <String, Object>{'data': 'SEMANTICS DATA'},
),
], web: true);
await terminalHandler.processTerminalInput('U');
expect(terminalHandler.logger.statusText, contains('SEMANTICS DATA'));
});
testWithoutContext(
'U - debugDumpSemanticsTreeInInverseHitTestOrder without service protocol is skipped',
() async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
supportsServiceProtocol: false,
);
await terminalHandler.processTerminalInput('U');
},
);
testWithoutContext('t,T - debugDumpRenderTree', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpRenderTree',
args: <String, Object>{'isolateId': '1'},
jsonResponse: <String, Object>{'data': 'RENDER DATA 1'},
),
// Request 2.
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpRenderTree',
args: <String, Object>{'isolateId': '1'},
jsonResponse: <String, Object>{'data': 'RENDER DATA 2'},
),
]);
await terminalHandler.processTerminalInput('t');
await terminalHandler.processTerminalInput('T');
expect(terminalHandler.logger.statusText, contains('RENDER DATA 1'));
expect(terminalHandler.logger.statusText, contains('RENDER DATA 2'));
});
testWithoutContext('t,T - debugDumpRenderTree with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpRenderTree',
args: <String, Object>{'isolateId': '1'},
jsonResponse: <String, Object>{'data': 'RENDER DATA 1'},
),
// Request 2.
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpRenderTree',
args: <String, Object>{'isolateId': '1'},
jsonResponse: <String, Object>{'data': 'RENDER DATA 2'},
),
], web: true);
await terminalHandler.processTerminalInput('t');
await terminalHandler.processTerminalInput('T');
expect(terminalHandler.logger.statusText, contains('RENDER DATA 1'));
expect(terminalHandler.logger.statusText, contains('RENDER DATA 2'));
});
testWithoutContext('t,T - debugDumpRenderTree without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
supportsServiceProtocol: false,
);
await terminalHandler.processTerminalInput('t');
await terminalHandler.processTerminalInput('T');
});
testWithoutContext('w,W - debugDumpApp', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpApp',
args: <String, Object>{'isolateId': '1'},
jsonResponse: <String, Object>{'data': 'WIDGET DATA 1'},
),
// Request 2.
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpApp',
args: <String, Object>{'isolateId': '1'},
jsonResponse: <String, Object>{'data': 'WIDGET DATA 2'},
),
]);
await terminalHandler.processTerminalInput('w');
await terminalHandler.processTerminalInput('W');
expect(terminalHandler.logger.statusText, contains('WIDGET DATA 1'));
expect(terminalHandler.logger.statusText, contains('WIDGET DATA 2'));
});
testWithoutContext('w,W - debugDumpApp with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpApp',
args: <String, Object>{'isolateId': '1'},
jsonResponse: <String, Object>{'data': 'WIDGET DATA 1'},
),
// Request 2.
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpApp',
args: <String, Object>{'isolateId': '1'},
jsonResponse: <String, Object>{'data': 'WIDGET DATA 2'},
),
], web: true);
await terminalHandler.processTerminalInput('w');
await terminalHandler.processTerminalInput('W');
expect(terminalHandler.logger.statusText, contains('WIDGET DATA 1'));
expect(terminalHandler.logger.statusText, contains('WIDGET DATA 2'));
});
testWithoutContext('v - launchDevToolsInBrowser', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[]);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
final FakeResidentDevtoolsHandler devtoolsHandler =
runner.residentDevtoolsHandler as FakeResidentDevtoolsHandler;
expect(devtoolsHandler.calledLaunchDevToolsInBrowser, isFalse);
await terminalHandler.processTerminalInput('v');
expect(devtoolsHandler.calledLaunchDevToolsInBrowser, isTrue);
});
testWithoutContext('w,W - debugDumpApp without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
supportsServiceProtocol: false,
);
await terminalHandler.processTerminalInput('w');
await terminalHandler.processTerminalInput('W');
});
testWithoutContext('z,Z - debugToggleDebugCheckElevationsEnabled', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugCheckElevationsEnabled',
args: <String, Object>{'isolateId': '1'},
),
// Request 2.
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugCheckElevationsEnabled',
args: <String, Object>{'isolateId': '1'},
),
]);
await terminalHandler.processTerminalInput('z');
await terminalHandler.processTerminalInput('Z');
});
testWithoutContext('z,Z - debugToggleDebugCheckElevationsEnabled with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugCheckElevationsEnabled',
args: <String, Object>{'isolateId': '1'},
),
// Request 2.
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugCheckElevationsEnabled',
args: <String, Object>{'isolateId': '1'},
),
], web: true);
await terminalHandler.processTerminalInput('z');
await terminalHandler.processTerminalInput('Z');
});
testWithoutContext(
'z,Z - debugToggleDebugCheckElevationsEnabled without service protocol is skipped',
() async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
supportsServiceProtocol: false,
);
await terminalHandler.processTerminalInput('z');
await terminalHandler.processTerminalInput('Z');
},
);
testWithoutContext('q,Q - exit', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[]);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await terminalHandler.processTerminalInput('q');
expect(runner.calledExit, true);
runner.calledExit = false;
await terminalHandler.processTerminalInput('Q');
expect(runner.calledExit, true);
});
testWithoutContext('r - hotReload unsupported', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
supportsHotReload: false,
);
await terminalHandler.processTerminalInput('r');
});
testWithoutContext('R - hotRestart unsupported', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
supportsRestart: false,
);
await terminalHandler.processTerminalInput('R');
});
testWithoutContext('r - hotReload', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[]);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await terminalHandler.processTerminalInput('r');
expect(runner.calledReload, true);
expect(runner.calledRestart, false);
});
testWithoutContext('R - hotRestart', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[]);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await terminalHandler.processTerminalInput('R');
expect(runner.calledReload, false);
expect(runner.calledRestart, true);
});
testWithoutContext('r - hotReload with non-fatal error', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
reloadExitCode: 1,
);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await terminalHandler.processTerminalInput('r');
expect(runner.calledReload, true);
expect(runner.calledRestart, false);
expect(
terminalHandler.logger.statusText,
contains('Try again after fixing the above error(s).'),
);
});
testWithoutContext('R - hotRestart with non-fatal error', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
reloadExitCode: 1,
);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await terminalHandler.processTerminalInput('R');
expect(runner.calledReload, false);
expect(runner.calledRestart, true);
expect(
terminalHandler.logger.statusText,
contains('Try again after fixing the above error(s).'),
);
});
testWithoutContext('r - hotReload with fatal error', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
reloadExitCode: 1,
fatalReloadError: true,
);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await expectLater(() => terminalHandler.processTerminalInput('r'), throwsToolExit());
expect(runner.calledReload, true);
expect(runner.calledRestart, false);
});
testWithoutContext('R - hotRestart with fatal error', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
reloadExitCode: 1,
fatalReloadError: true,
);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await expectLater(() => terminalHandler.processTerminalInput('R'), throwsToolExit());
expect(runner.calledReload, false);
expect(runner.calledRestart, true);
});
});
testWithoutContext('ResidentRunner clears the screen when it should', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
reloadExitCode: 1,
fatalReloadError: true,
);
const String message = 'This should be cleared';
expect(terminalHandler.logger.statusText, equals(''));
terminalHandler.logger.printStatus(message);
expect(terminalHandler.logger.statusText, equals('$message\n')); // printStatus makes a newline
await terminalHandler.processTerminalInput('c');
expect(terminalHandler.logger.statusText, equals(''));
});
testWithoutContext('s, can take screenshot on debug device that supports screenshot', () async {
final BufferLogger logger = BufferLogger.test();
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[
listViews,
FakeVmServiceRequest(
method: 'ext.flutter.debugAllowBanner',
args: <String, Object?>{'isolateId': fakeUnpausedIsolate.id, 'enabled': 'false'},
),
FakeVmServiceRequest(
method: 'ext.flutter.debugAllowBanner',
args: <String, Object?>{'isolateId': fakeUnpausedIsolate.id, 'enabled': 'true'},
),
],
logger: logger,
supportsScreenshot: true,
);
await terminalHandler.processTerminalInput('s');
expect(logger.statusText, contains('Screenshot written to flutter_01.png (0kB)'));
});
testWithoutContext(
's, will not take screenshot on non-web device without screenshot tooling support',
() async {
final BufferLogger logger = BufferLogger.test();
final FileSystem fileSystem = MemoryFileSystem.test();
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
logger: logger,
fileSystem: fileSystem,
);
await terminalHandler.processTerminalInput('s');
expect(logger.statusText, isNot(contains('Screenshot written to')));
},
);
testWithoutContext(
's, can take screenshot on debug web device that does not support screenshot',
() async {
final BufferLogger logger = BufferLogger.test();
final FileSystem fileSystem = MemoryFileSystem.test();
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[
listViews,
FakeVmServiceRequest(
method: 'ext.flutter.debugAllowBanner',
args: <String, Object?>{'isolateId': fakeUnpausedIsolate.id, 'enabled': 'false'},
),
FakeVmServiceRequest(
method: 'ext.dwds.screenshot',
args: <String, Object>{},
jsonResponse: <String, Object>{
'data': base64.encode(<int>[1, 2, 3, 4]),
},
),
FakeVmServiceRequest(
method: 'ext.flutter.debugAllowBanner',
args: <String, Object?>{'isolateId': fakeUnpausedIsolate.id, 'enabled': 'true'},
),
],
logger: logger,
web: true,
fileSystem: fileSystem,
);
await terminalHandler.processTerminalInput('s');
expect(logger.statusText, contains('Screenshot written to flutter_01.png (0kB)'));
expect(fileSystem.currentDirectory.childFile('flutter_01.png').readAsBytesSync(), <int>[
1,
2,
3,
4,
]);
},
);
testWithoutContext(
's, can take screenshot on device that does not support service protocol',
() async {
final BufferLogger logger = BufferLogger.test();
final FileSystem fileSystem = MemoryFileSystem.test();
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
logger: logger,
supportsScreenshot: true,
supportsServiceProtocol: false,
fileSystem: fileSystem,
);
await terminalHandler.processTerminalInput('s');
expect(logger.statusText, contains('Screenshot written to flutter_01.png (0kB)'));
expect(fileSystem.currentDirectory.childFile('flutter_01.png').readAsBytesSync(), <int>[
1,
2,
3,
4,
]);
},
);
testWithoutContext(
's, does not take a screenshot on a device that does not support screenshot or the service protocol',
() async {
final BufferLogger logger = BufferLogger.test();
final FileSystem fileSystem = MemoryFileSystem.test();
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
logger: logger,
supportsServiceProtocol: false,
fileSystem: fileSystem,
);
await terminalHandler.processTerminalInput('s');
expect(logger.statusText, '\n');
expect(fileSystem.currentDirectory.childFile('flutter_01.png'), isNot(exists));
},
);
testWithoutContext(
's, does not take a screenshot on a web device that does not support screenshot or the service protocol',
() async {
final BufferLogger logger = BufferLogger.test();
final FileSystem fileSystem = MemoryFileSystem.test();
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[],
logger: logger,
supportsServiceProtocol: false,
web: true,
fileSystem: fileSystem,
);
await terminalHandler.processTerminalInput('s');
expect(logger.statusText, '\n');
expect(fileSystem.currentDirectory.childFile('flutter_01.png'), isNot(exists));
},
);
testWithoutContext(
's, bails taking screenshot on debug device if dwds.screenshot throws RpcError, restoring banner',
() async {
final BufferLogger logger = BufferLogger.test();
final FileSystem fileSystem = MemoryFileSystem.test();
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[
listViews,
FakeVmServiceRequest(
method: 'ext.flutter.debugAllowBanner',
args: <String, Object?>{'isolateId': fakeUnpausedIsolate.id, 'enabled': 'false'},
),
FakeVmServiceRequest(
method: 'ext.dwds.screenshot',
// Failed response,
error: FakeRPCError(code: vm_service.RPCErrorKind.kInternalError.code),
),
FakeVmServiceRequest(
method: 'ext.flutter.debugAllowBanner',
args: <String, Object?>{'isolateId': fakeUnpausedIsolate.id, 'enabled': 'true'},
),
],
logger: logger,
web: true,
fileSystem: fileSystem,
);
await terminalHandler.processTerminalInput('s');
expect(logger.errorText, contains('Error'));
},
);
testWithoutContext(
's, bails taking screenshot on debug device if debugAllowBanner during second request',
() async {
final BufferLogger logger = BufferLogger.test();
final FileSystem fileSystem = MemoryFileSystem.test();
final TerminalHandler terminalHandler = setUpTerminalHandler(
<FakeVmServiceRequest>[
listViews,
FakeVmServiceRequest(
method: 'ext.flutter.debugAllowBanner',
args: <String, Object?>{'isolateId': fakeUnpausedIsolate.id, 'enabled': 'false'},
),
FakeVmServiceRequest(
method: 'ext.flutter.debugAllowBanner',
args: <String, Object?>{'isolateId': fakeUnpausedIsolate.id, 'enabled': 'true'},
// Failed response,
error: FakeRPCError(code: vm_service.RPCErrorKind.kInternalError.code),
),
],
logger: logger,
supportsScreenshot: true,
fileSystem: fileSystem,
);
await terminalHandler.processTerminalInput('s');
expect(logger.errorText, contains('Error'));
},
);
testWithoutContext('pidfile creation', () {
final BufferLogger testLogger = BufferLogger.test();
final Signals signals = _TestSignals(Signals.defaultExitSignals);
final Terminal terminal = Terminal.test();
final MemoryFileSystem fs = MemoryFileSystem.test();
final ProcessInfo processInfo = ProcessInfo.test(fs);
final FakeResidentRunner residentRunner = FakeResidentRunner(
FlutterDevice(
FakeDevice(),
buildInfo: BuildInfo.debug,
generator: FakeResidentCompiler(),
developmentShaderCompiler: const FakeShaderCompiler(),
),
testLogger,
fs,
);
residentRunner
..supportsRestart = true
..supportsServiceProtocol = true
..stayResident = true;
const String filename = 'test.pid';
final TerminalHandler terminalHandler = TerminalHandler(
residentRunner,
logger: testLogger,
signals: signals,
terminal: terminal,
processInfo: processInfo,
reportReady: false,
pidFile: filename,
);
expect(fs.file(filename), isNot(exists));
terminalHandler.setupTerminal();
terminalHandler.registerSignalHandlers();
expect(fs.file(filename), exists);
terminalHandler.stop();
expect(fs.file(filename), isNot(exists));
});
}
class FakeResidentRunner extends ResidentHandlers {
FakeResidentRunner(FlutterDevice device, this.logger, this.fileSystem)
: flutterDevices = <FlutterDevice>[device];
bool calledDetach = false;
bool calledPrint = false;
bool calledExit = false;
bool calledPrintWithDetails = false;
bool calledReload = false;
bool calledRestart = false;
int reloadExitCode = 0;
bool fatalReloadError = false;
@override
final Logger logger;
@override
final FileSystem fileSystem;
@override
final List<FlutterDevice> flutterDevices;
@override
bool canHotReload = true;
@override
bool hotMode = true;
@override
bool isRunningDebug = true;
@override
bool isRunningProfile = false;
@override
bool isRunningRelease = false;
@override
bool stayResident = true;
@override
bool supportsRestart = true;
@override
bool supportsServiceProtocol = true;
@override
bool supportsWriteSkSL = true;
@override
Future<void> cleanupAfterSignal() async {}
@override
Future<void> detach() async {
calledDetach = true;
}
@override
Future<void> exit() async {
calledExit = true;
}
@override
void printHelp({required bool details}) {
if (details) {
calledPrintWithDetails = true;
} else {
calledPrint = true;
}
}
@override
Future<void> runSourceGenerators() async {}
@override
Future<OperationResult> restart({
bool fullRestart = false,
bool pause = false,
String? reason,
}) async {
if (fullRestart && !supportsRestart) {
throw StateError('illegal restart');
}
if (!fullRestart && !canHotReload) {
throw StateError('illegal reload');
}
if (fullRestart) {
calledRestart = true;
} else {
calledReload = true;
}
return OperationResult(reloadExitCode, '', fatal: fatalReloadError);
}
// TODO(bkonyi): remove when ready to serve DevTools from DDS.
@override
ResidentDevtoolsHandler get residentDevtoolsHandler => _residentDevtoolsHandler;
final ResidentDevtoolsHandler _residentDevtoolsHandler = FakeResidentDevtoolsHandler();
}
// TODO(bkonyi): remove when ready to serve DevTools from DDS.
class FakeResidentDevtoolsHandler extends Fake implements ResidentDevtoolsHandler {
bool calledLaunchDevToolsInBrowser = false;
@override
bool launchDevToolsInBrowser({List<FlutterDevice?>? flutterDevices}) {
return calledLaunchDevToolsInBrowser = true;
}
}
class FakeDevice extends Fake implements Device {
@override
bool isSupported() => true;
@override
bool supportsScreenshot = false;
@override
String get name => 'Fake Device';
@override
String get displayName => name;
@override
DartDevelopmentService dds = DartDevelopmentService(logger: FakeLogger());
@override
Future<void> takeScreenshot(File file) async {
if (!supportsScreenshot) {
throw StateError('illegal screenshot attempt');
}
file.writeAsBytesSync(<int>[1, 2, 3, 4]);
}
}
TerminalHandler setUpTerminalHandler(
List<FakeVmServiceRequest> requests, {
bool supportsRestart = true,
bool supportsServiceProtocol = true,
bool supportsHotReload = true,
bool web = false,
bool fatalReloadError = false,
bool supportsScreenshot = false,
int reloadExitCode = 0,
BuildMode buildMode = BuildMode.debug,
Logger? logger,
FileSystem? fileSystem,
}) {
final Logger testLogger = logger ?? BufferLogger.test();
final Signals signals = Signals.test();
final Terminal terminal = Terminal.test();
final FileSystem localFileSystem = fileSystem ?? MemoryFileSystem.test();
final ProcessInfo processInfo = ProcessInfo.test(MemoryFileSystem.test());
final FlutterDevice device = FlutterDevice(
FakeDevice()..supportsScreenshot = supportsScreenshot,
buildInfo: BuildInfo(
buildMode,
'',
treeShakeIcons: false,
packageConfigPath: '.dart_tool/package_config.json',
),
generator: FakeResidentCompiler(),
developmentShaderCompiler: const FakeShaderCompiler(),
targetPlatform: web ? TargetPlatform.web_javascript : TargetPlatform.android_arm,
);
device.vmService = FakeVmServiceHost(requests: requests).vmService;
final FakeResidentRunner residentRunner =
FakeResidentRunner(device, testLogger, localFileSystem)
..supportsServiceProtocol = supportsServiceProtocol
..supportsRestart = supportsRestart
..canHotReload = supportsHotReload
..fatalReloadError = fatalReloadError
..reloadExitCode = reloadExitCode;
switch (buildMode) {
case BuildMode.debug:
residentRunner
..isRunningDebug = true
..isRunningProfile = false
..isRunningRelease = false;
case BuildMode.profile:
residentRunner
..isRunningDebug = false
..isRunningProfile = true
..isRunningRelease = false;
case BuildMode.release:
residentRunner
..isRunningDebug = false
..isRunningProfile = false
..isRunningRelease = true;
case _:
// NOOP
}
return TerminalHandler(
residentRunner,
logger: testLogger,
signals: signals,
terminal: terminal,
processInfo: processInfo,
reportReady: false,
);
}
class FakeResidentCompiler extends Fake implements ResidentCompiler {}
class TestRunner extends Fake implements ResidentRunner {
bool hasHelpBeenPrinted = false;
@override
Future<void> cleanupAfterSignal() async {}
@override
Future<void> cleanupAtFinish() async {}
@override
void printHelp({bool? details}) {
hasHelpBeenPrinted = true;
}
@override
Future<int?> run({
Completer<DebugConnectionInfo>? connectionInfoCompleter,
Completer<void>? appStartedCompleter,
bool enableDevTools = false,
String? route,
}) async => null;
@override
Future<int?> attach({
Completer<DebugConnectionInfo>? connectionInfoCompleter,
Completer<void>? appStartedCompleter,
bool allowExistingDdsInstance = false,
bool enableDevTools = false,
bool needsFullRestart = true,
}) async => null;
}
class _TestSignals implements Signals {
_TestSignals(this.exitSignals);
final List<ProcessSignal> exitSignals;
final Map<ProcessSignal, Map<Object, SignalHandler>> _handlersTable =
<ProcessSignal, Map<Object, SignalHandler>>{};
@override
Object addHandler(ProcessSignal signal, SignalHandler handler) {
final Object token = Object();
_handlersTable.putIfAbsent(signal, () => <Object, SignalHandler>{})[token] = handler;
return token;
}
@override
Future<bool> removeHandler(ProcessSignal signal, Object token) async {
if (!_handlersTable.containsKey(signal)) {
return false;
}
if (!_handlersTable[signal]!.containsKey(token)) {
return false;
}
_handlersTable[signal]!.remove(token);
return true;
}
@override
Stream<Object> get errors => _errors.stream;
final StreamController<Object> _errors = StreamController<Object>();
}
class FakeShaderCompiler implements DevelopmentShaderCompiler {
const FakeShaderCompiler();
@override
void configureCompiler(TargetPlatform? platform) {}
@override
Future<DevFSContent> recompileShader(DevFSContent inputShader) {
throw UnimplementedError();
}
}