[ Widget Preview ] Update generated scaffold project to include early preview rendering (#162847)
With this change, `flutter widget-preview start` will launch a working widget preview environment that can render previews from a target project. Also fixes an issue where `--offline` wasn't being respected by some pub operations.
This commit is contained in:
parent
94cd4b14c9
commit
e7e5480a57
@ -124,7 +124,7 @@ class LocalSignals implements Signals {
|
||||
|
||||
// If _handlersList[signal] is empty, then lookup the cached stream
|
||||
// controller and unsubscribe from the stream.
|
||||
if (_handlersList.isEmpty) {
|
||||
if (_handlersList[signal]!.isEmpty) {
|
||||
await _streamSubscriptions[signal]?.cancel();
|
||||
}
|
||||
return true;
|
||||
|
@ -491,9 +491,26 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
|
||||
await pub.interactively(
|
||||
<String>[
|
||||
pubAdd,
|
||||
if (offline) '--offline',
|
||||
'--directory',
|
||||
widgetPreviewScaffoldProject.directory.path,
|
||||
'${rootProject.manifest.appName}:{"path":${rootProject.directory.path}}',
|
||||
// Ensure the path using POSIX separators, otherwise the "path_not_posix" check will fail.
|
||||
'${rootProject.manifest.appName}:{"path":${rootProject.directory.path.replaceAll(r"\", "/")}}',
|
||||
],
|
||||
context: PubContext.pubAdd,
|
||||
command: pubAdd,
|
||||
touchesPackageConfig: true,
|
||||
);
|
||||
|
||||
// Adds a dependency on flutter_lints, which is referenced by the
|
||||
// analysis_options.yaml generated by the 'app' template.
|
||||
await pub.interactively(
|
||||
<String>[
|
||||
pubAdd,
|
||||
if (offline) '--offline',
|
||||
'--directory',
|
||||
widgetPreviewScaffoldProject.directory.path,
|
||||
'flutter_lints',
|
||||
],
|
||||
context: PubContext.pubAdd,
|
||||
command: pubAdd,
|
||||
|
@ -19,7 +19,7 @@ class PreviewCodeGenerator {
|
||||
/// project.
|
||||
final FlutterProject widgetPreviewScaffoldProject;
|
||||
|
||||
static const String generatedPreviewFilePath = 'lib/generated_preview.dart';
|
||||
static const String generatedPreviewFilePath = 'lib/src/generated_preview.dart';
|
||||
|
||||
/// Generates code used by the widget preview scaffold based on the preview instances listed in
|
||||
/// [previews].
|
||||
@ -43,10 +43,7 @@ class PreviewCodeGenerator {
|
||||
void populatePreviewsInGeneratedPreviewScaffold(PreviewMapping previews) {
|
||||
final Library lib = Library(
|
||||
(LibraryBuilder b) => b.body.addAll(<Spec>[
|
||||
Directive.import(
|
||||
// TODO(bkonyi): update with actual location in the framework
|
||||
'package:widget_preview/widget_preview.dart',
|
||||
),
|
||||
Directive.import('package:flutter/widgets.dart'),
|
||||
Method(
|
||||
(MethodBuilder b) =>
|
||||
b
|
||||
|
@ -353,6 +353,10 @@
|
||||
"templates/plugin_swift_package_manager/macos.tmpl/projectName.tmpl/Sources/projectName.tmpl/PrivacyInfo.xcprivacy",
|
||||
|
||||
"templates/widget_preview_scaffold/lib/main.dart.tmpl",
|
||||
"templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl",
|
||||
"templates/widget_preview_scaffold/lib/src/controls.dart.tmpl",
|
||||
"templates/widget_preview_scaffold/lib/src/generated_preview.dart.tmpl",
|
||||
"templates/widget_preview_scaffold/lib/src/utils.dart.tmpl",
|
||||
"templates/widget_preview_scaffold/pubspec.yaml.tmpl",
|
||||
"templates/widget_preview_scaffold/README.md.tmpl",
|
||||
|
||||
|
@ -2,25 +2,9 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// TODO(bkonyi): Implement.
|
||||
import 'src/widget_preview_rendering.dart';
|
||||
import 'src/generated_preview.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MainApp());
|
||||
}
|
||||
|
||||
class MainApp extends StatelessWidget {
|
||||
const MainApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: Text('Hello World!'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
Future<void> main() async {
|
||||
await mainImpl(previewsProvider: previews);
|
||||
}
|
||||
|
@ -0,0 +1,102 @@
|
||||
// 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 'package:flutter/material.dart';
|
||||
|
||||
class _WidgetPreviewIconButton extends StatelessWidget {
|
||||
const _WidgetPreviewIconButton({
|
||||
required this.tooltip,
|
||||
required this.onPressed,
|
||||
required this.icon,
|
||||
});
|
||||
|
||||
final String tooltip;
|
||||
final void Function()? onPressed;
|
||||
final IconData icon;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Tooltip(
|
||||
message: tooltip,
|
||||
child: Ink(
|
||||
decoration: ShapeDecoration(
|
||||
shape: const CircleBorder(),
|
||||
color: onPressed != null ? Colors.lightBlue : Colors.grey,
|
||||
),
|
||||
child: IconButton(
|
||||
onPressed: onPressed,
|
||||
icon: Icon(
|
||||
color: Colors.white,
|
||||
icon,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides controls to change the zoom level of a [WidgetPreview].
|
||||
class ZoomControls extends StatelessWidget {
|
||||
/// Provides controls to change the zoom level of a [WidgetPreview].
|
||||
const ZoomControls({
|
||||
super.key,
|
||||
required TransformationController transformationController,
|
||||
}) : _transformationController = transformationController;
|
||||
|
||||
final TransformationController _transformationController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
_WidgetPreviewIconButton(
|
||||
tooltip: 'Zoom in',
|
||||
onPressed: _zoomIn,
|
||||
icon: Icons.zoom_in,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
_WidgetPreviewIconButton(
|
||||
tooltip: 'Zoom out',
|
||||
onPressed: _zoomOut,
|
||||
icon: Icons.zoom_out,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
_WidgetPreviewIconButton(
|
||||
tooltip: 'Reset zoom',
|
||||
onPressed: _reset,
|
||||
icon: Icons.refresh,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _zoomIn() {
|
||||
_transformationController.value = Matrix4.copy(
|
||||
_transformationController.value,
|
||||
).scaled(1.1);
|
||||
}
|
||||
|
||||
void _zoomOut() {
|
||||
final Matrix4 updated = Matrix4.copy(
|
||||
_transformationController.value,
|
||||
).scaled(0.9);
|
||||
|
||||
// Don't allow for zooming out past the original size of the widget.
|
||||
// Assumes scaling is evenly applied to the entire matrix.
|
||||
if (updated.entry(0, 0) < 1.0) {
|
||||
updated.setIdentity();
|
||||
}
|
||||
|
||||
_transformationController.value = updated;
|
||||
}
|
||||
|
||||
void _reset() {
|
||||
_transformationController.value = Matrix4.identity();
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
// 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 'package:flutter/widgets.dart';
|
||||
|
||||
List<WidgetPreview> previews() => <WidgetPreview>[];
|
@ -0,0 +1,18 @@
|
||||
// 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 'package:flutter/material.dart';
|
||||
|
||||
/// A basic vertical spacer.
|
||||
class VerticalSpacer extends StatelessWidget {
|
||||
/// Creates a basic vertical spacer.
|
||||
const VerticalSpacer({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const SizedBox(
|
||||
height: 10,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,364 @@
|
||||
// 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 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'controls.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
class WidgetPreviewWidget extends StatefulWidget {
|
||||
const WidgetPreviewWidget({
|
||||
super.key,
|
||||
required this.preview,
|
||||
});
|
||||
|
||||
final WidgetPreview preview;
|
||||
|
||||
@override
|
||||
State<WidgetPreviewWidget> createState() => _WidgetPreviewWidgetState();
|
||||
}
|
||||
|
||||
class _WidgetPreviewWidgetState extends State<WidgetPreviewWidget> {
|
||||
final transformationController = TransformationController();
|
||||
final deviceOrientation = ValueNotifier<Orientation>(Orientation.portrait);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final previewerConstraints =
|
||||
WidgetPreviewerWindowConstraints.getRootConstraints(context);
|
||||
|
||||
final maxSizeConstraints = previewerConstraints.copyWith(
|
||||
minHeight: previewerConstraints.maxHeight / 2.0,
|
||||
maxHeight: previewerConstraints.maxHeight / 2.0,
|
||||
);
|
||||
|
||||
Widget preview = _WidgetPreviewWrapper(
|
||||
previewerConstraints: maxSizeConstraints,
|
||||
child: SizedBox(
|
||||
width: widget.preview.width,
|
||||
height: widget.preview.height,
|
||||
child: widget.preview.child,
|
||||
),
|
||||
);
|
||||
|
||||
preview = MediaQuery(
|
||||
data: _buildMediaQueryOverride(),
|
||||
child: preview,
|
||||
);
|
||||
|
||||
preview = Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (widget.preview.name != null) ...[
|
||||
Text(
|
||||
widget.preview.name!,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w300,
|
||||
),
|
||||
),
|
||||
const VerticalSpacer(),
|
||||
],
|
||||
InteractiveViewerWrapper(
|
||||
transformationController: transformationController,
|
||||
child: preview,
|
||||
),
|
||||
const VerticalSpacer(),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ZoomControls(
|
||||
transformationController: transformationController,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 16.0,
|
||||
horizontal: 16.0,
|
||||
),
|
||||
child: preview,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
MediaQueryData _buildMediaQueryOverride() {
|
||||
var mediaQueryData = MediaQuery.of(context);
|
||||
|
||||
if (widget.preview.textScaleFactor != null) {
|
||||
mediaQueryData = mediaQueryData.copyWith(
|
||||
textScaler: TextScaler.linear(widget.preview.textScaleFactor!),
|
||||
);
|
||||
}
|
||||
|
||||
var size = Size(widget.preview.width ?? mediaQueryData.size.width,
|
||||
widget.preview.height ?? mediaQueryData.size.height);
|
||||
|
||||
if (widget.preview.width != null || widget.preview.height != null) {
|
||||
mediaQueryData = mediaQueryData.copyWith(
|
||||
size: size,
|
||||
);
|
||||
}
|
||||
|
||||
return mediaQueryData;
|
||||
}
|
||||
}
|
||||
|
||||
/// An [InheritedWidget] that propagates the current size of the
|
||||
/// WidgetPreviewScaffold.
|
||||
///
|
||||
/// This is needed when determining how to put constraints on previewed widgets
|
||||
/// that would otherwise have infinite constraints.
|
||||
class WidgetPreviewerWindowConstraints extends InheritedWidget {
|
||||
const WidgetPreviewerWindowConstraints({
|
||||
super.key,
|
||||
required super.child,
|
||||
required this.constraints,
|
||||
});
|
||||
|
||||
final BoxConstraints constraints;
|
||||
|
||||
static BoxConstraints getRootConstraints(BuildContext context) {
|
||||
final result = context
|
||||
.dependOnInheritedWidgetOfExactType<WidgetPreviewerWindowConstraints>();
|
||||
assert(
|
||||
result != null,
|
||||
'No WidgetPreviewerWindowConstraints founds in context',
|
||||
);
|
||||
return result!.constraints;
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(WidgetPreviewerWindowConstraints oldWidget) {
|
||||
return oldWidget.constraints != constraints;
|
||||
}
|
||||
}
|
||||
|
||||
class InteractiveViewerWrapper extends StatelessWidget {
|
||||
const InteractiveViewerWrapper({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.transformationController,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final TransformationController transformationController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InteractiveViewer(
|
||||
transformationController: transformationController,
|
||||
scaleEnabled: false,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(bkonyi): according to goderbauer@, this probably isn't the best approach to ensure we
|
||||
// handle unconstrained widgets. This should be reworked.
|
||||
/// Wrapper applying a custom render object to force constraints on
|
||||
/// unconstrained widgets.
|
||||
class _WidgetPreviewWrapper extends SingleChildRenderObjectWidget {
|
||||
const _WidgetPreviewWrapper({
|
||||
super.child,
|
||||
required this.previewerConstraints,
|
||||
});
|
||||
|
||||
/// The size of the previewer render surface.
|
||||
final BoxConstraints previewerConstraints;
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
return _WidgetPreviewWrapperBox(
|
||||
previewerConstraints: previewerConstraints,
|
||||
child: null,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(
|
||||
BuildContext context,
|
||||
_WidgetPreviewWrapperBox renderObject,
|
||||
) {
|
||||
renderObject.setPreviewerConstraints(previewerConstraints);
|
||||
}
|
||||
}
|
||||
|
||||
/// Custom render box that forces constraints onto unconstrained widgets.
|
||||
class _WidgetPreviewWrapperBox extends RenderShiftedBox {
|
||||
_WidgetPreviewWrapperBox({
|
||||
required RenderBox? child,
|
||||
required BoxConstraints previewerConstraints,
|
||||
}) : _previewerConstraints = previewerConstraints,
|
||||
super(child);
|
||||
|
||||
BoxConstraints _constraintOverride = const BoxConstraints();
|
||||
BoxConstraints _previewerConstraints;
|
||||
|
||||
void setPreviewerConstraints(BoxConstraints previewerConstraints) {
|
||||
if (_previewerConstraints == previewerConstraints) {
|
||||
return;
|
||||
}
|
||||
_previewerConstraints = previewerConstraints;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
@override
|
||||
void layout(
|
||||
Constraints constraints, {
|
||||
bool parentUsesSize = false,
|
||||
}) {
|
||||
if (child != null && constraints is BoxConstraints) {
|
||||
double minInstrinsicHeight;
|
||||
try {
|
||||
minInstrinsicHeight = child!.getMinIntrinsicHeight(
|
||||
constraints.maxWidth,
|
||||
);
|
||||
} on Object {
|
||||
minInstrinsicHeight = 0.0;
|
||||
}
|
||||
// Determine if the previewed widget is vertically constrained. If the
|
||||
// widget has a minimum intrinsic height of zero given the widget's max
|
||||
// width, it has an unconstrained height and will cause an overflow in
|
||||
// the previewer. In this case, apply finite constraints (e.g., the
|
||||
// constraints for the root of the previewer). Otherwise, use the
|
||||
// widget's actual constraints.
|
||||
_constraintOverride = minInstrinsicHeight == 0
|
||||
? _previewerConstraints
|
||||
: const BoxConstraints();
|
||||
}
|
||||
super.layout(
|
||||
constraints,
|
||||
parentUsesSize: parentUsesSize,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
final child = this.child;
|
||||
if (child == null) {
|
||||
size = Size.zero;
|
||||
return;
|
||||
}
|
||||
final updatedConstraints = _constraintOverride.enforce(constraints);
|
||||
child.layout(
|
||||
updatedConstraints,
|
||||
parentUsesSize: true,
|
||||
);
|
||||
size = constraints.constrain(child.size);
|
||||
}
|
||||
}
|
||||
|
||||
/// Custom [AssetBundle] used to map original asset paths from the parent
|
||||
/// project to those in the preview project.
|
||||
class PreviewAssetBundle extends PlatformAssetBundle {
|
||||
// Assets shipped via package dependencies have paths that start with
|
||||
// 'packages'.
|
||||
static const String _kPackagesPrefix = 'packages';
|
||||
|
||||
@override
|
||||
Future<ByteData> load(String key) {
|
||||
// These assets are always present or are shipped via a package and aren't
|
||||
// actually located in the parent project, meaning their paths did not need
|
||||
// to be modified.
|
||||
if (key == 'AssetManifest.bin' ||
|
||||
key == 'AssetManifest.json' ||
|
||||
key == 'FontManifest.json' ||
|
||||
key.startsWith(_kPackagesPrefix)) {
|
||||
return super.load(key);
|
||||
}
|
||||
// Other assets are from the parent project. Map their keys to those found
|
||||
// in the pubspec.yaml of the preview envirnment.
|
||||
return super.load('../../$key');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ImmutableBuffer> loadBuffer(String key) async {
|
||||
return await ImmutableBuffer.fromAsset(
|
||||
key.startsWith(_kPackagesPrefix) ? key : '../../$key',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Main entrypoint for the widget previewer.
|
||||
///
|
||||
/// We don't actually define this as `main` to avoid copying this file into
|
||||
/// the preview scaffold project which prevents us from being able to use hot
|
||||
/// restart to iterate on this file.
|
||||
Future<void> mainImpl({
|
||||
required List<WidgetPreview> Function() previewsProvider,
|
||||
}) async {
|
||||
runApp(
|
||||
_WidgetPreviewScaffold(
|
||||
previewsProvider: previewsProvider,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _WidgetPreviewScaffold extends StatelessWidget {
|
||||
const _WidgetPreviewScaffold({required this.previewsProvider});
|
||||
|
||||
final List<WidgetPreview> Function() previewsProvider;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<WidgetPreview> previewList = previewsProvider();
|
||||
Widget previewView;
|
||||
if (previewList.isEmpty) {
|
||||
previewView = const Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Center(
|
||||
// TODO: consider including details on how to get started
|
||||
// with Widget Previews.
|
||||
child: Text(
|
||||
'No previews available',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
previewView = LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
return WidgetPreviewerWindowConstraints(
|
||||
constraints: constraints,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
for (final WidgetPreview preview in previewList) WidgetPreviewWidget(preview: preview),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: Material(
|
||||
color: Colors.transparent,
|
||||
child: DefaultAssetBundle(
|
||||
bundle: PreviewAssetBundle(),
|
||||
child: previewView,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ import 'dart:convert';
|
||||
import 'package:flutter_tools/src/artifacts.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/io.dart';
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||
import 'package:process/process.dart';
|
||||
|
||||
@ -47,7 +48,7 @@ Future<void> analyzeProject(
|
||||
List<String> expectedFailures = const <String>[],
|
||||
}) async {
|
||||
final String flutterToolsSnapshotPath = globals.fs.path.absolute(
|
||||
globals.fs.path.join('..', '..', 'bin', 'cache', 'flutter_tools.snapshot'),
|
||||
globals.fs.path.join(Cache.flutterRoot!, 'bin', 'cache', 'flutter_tools.snapshot'),
|
||||
);
|
||||
|
||||
final List<String> args = <String>[flutterToolsSnapshotPath, 'analyze'];
|
||||
|
@ -31,11 +31,12 @@ void main() {
|
||||
late LoggingProcessManager loggingProcessManager;
|
||||
late FakeStdio mockStdio;
|
||||
late Logger logger;
|
||||
late FileSystem fs;
|
||||
late LocalFileSystem fs;
|
||||
late BotDetector botDetector;
|
||||
late Platform platform;
|
||||
|
||||
setUp(() {
|
||||
setUp(() async {
|
||||
await ensureFlutterToolsSnapshot();
|
||||
loggingProcessManager = LoggingProcessManager();
|
||||
logger = BufferLogger.test();
|
||||
fs = LocalFileSystem.test(signals: Signals.test());
|
||||
@ -43,10 +44,16 @@ void main() {
|
||||
tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_create_test.');
|
||||
mockStdio = FakeStdio();
|
||||
platform = FakePlatform.fromPlatform(const LocalPlatform());
|
||||
// Most, but not all, tests will run some variant of "pub get" after creation,
|
||||
// which in turn will check for the presence of the Flutter SDK root. Without
|
||||
// this field set consistently, the order of the tests becomes important *or*
|
||||
// you need to remember to set it everywhere.
|
||||
Cache.flutterRoot = fs.path.absolute('..', '..');
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
tryToDelete(tempDir);
|
||||
fs.dispose();
|
||||
});
|
||||
|
||||
Future<Directory> createRootProject() async {
|
||||
@ -91,11 +98,13 @@ void main() {
|
||||
final Directory widgetPreviewScaffoldDir = widgetPreviewScaffoldFromRootProject(
|
||||
rootProject: rootProject ?? fs.currentDirectory,
|
||||
);
|
||||
expect(widgetPreviewScaffoldDir, exists);
|
||||
expect(
|
||||
widgetPreviewScaffoldDir.childFile(PreviewCodeGenerator.generatedPreviewFilePath),
|
||||
exists,
|
||||
);
|
||||
// Don't perform analysis on Windows since `dart pub add` will use '\' for
|
||||
// path dependencies and cause analysis to fail.
|
||||
// TODO(bkonyi): enable analysis on Windows once https://github.com/dart-lang/pub/issues/4520
|
||||
// is resolved.
|
||||
if (!platform.isWindows) {
|
||||
await analyzeProject(widgetPreviewScaffoldDir.path);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> cleanWidgetPreview({required Directory rootProject}) async {
|
||||
@ -181,13 +190,12 @@ void main() {
|
||||
);
|
||||
|
||||
const String samplePreviewFile = '''
|
||||
// This doesn't need to be valid code for testing as long as it has the @Preview() annotation
|
||||
@Preview()
|
||||
WidgetPreview preview() => WidgetPreview();''';
|
||||
WidgetPreview preview() => const WidgetPreview(child: Text('Foo'));''';
|
||||
|
||||
const String expectedGeneratedFileContents = '''
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:flutter_project/foo.dart' as _i1;import 'package:widget_preview/widget_preview.dart';List<WidgetPreview> previews() => [_i1.preview()];''';
|
||||
import 'package:flutter_project/foo.dart' as _i1;import 'package:flutter/widgets.dart';List<WidgetPreview> previews() => [_i1.preview()];''';
|
||||
|
||||
testUsingContext(
|
||||
'start finds existing previews and injects them into ${PreviewCodeGenerator.generatedPreviewFilePath}',
|
||||
|
@ -25,7 +25,7 @@ void main() {
|
||||
final Directory projectDir =
|
||||
fs.currentDirectory.childDirectory('project')
|
||||
..createSync()
|
||||
..childDirectory('lib').createSync();
|
||||
..childDirectory('lib/src').createSync(recursive: true);
|
||||
project = FlutterProject(projectDir, manifest, manifest);
|
||||
codeGenerator = PreviewCodeGenerator(widgetPreviewScaffoldProject: project, fs: fs);
|
||||
});
|
||||
@ -56,7 +56,7 @@ void main() {
|
||||
// The generated file is unfortunately unformatted.
|
||||
const String expectedGeneratedPreviewFileContents = '''
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'foo.dart' as _i1;import 'src/bar.dart' as _i2;import 'package:widget_preview/widget_preview.dart';List<WidgetPreview> previews() => [_i1.preview(), _i2.barPreview1(), _i2.barPreview2(), ];''';
|
||||
import 'foo.dart' as _i1;import 'src/bar.dart' as _i2;import 'package:flutter/widgets.dart';List<WidgetPreview> previews() => [_i1.preview(), _i2.barPreview1(), _i2.barPreview2(), ];''';
|
||||
expect(generatedPreviewFile.readAsStringSync(), expectedGeneratedPreviewFileContents);
|
||||
|
||||
// Regenerate the generated file with no previews.
|
||||
@ -69,7 +69,7 @@ import 'foo.dart' as _i1;import 'src/bar.dart' as _i2;import 'package:widget_pre
|
||||
// - An import of the widget preview library
|
||||
// - A top-level function 'List<WidgetPreview> previews()' that returns an empty list.
|
||||
const String emptyGeneratedPreviewFileContents = '''
|
||||
import 'package:widget_preview/widget_preview.dart';List<WidgetPreview> previews() => [];''';
|
||||
import 'package:flutter/widgets.dart';List<WidgetPreview> previews() => [];''';
|
||||
expect(generatedPreviewFile.readAsStringSync(), emptyGeneratedPreviewFileContents);
|
||||
},
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user