| // Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file |
| // for details. 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:analyzer/src/diagnostic/diagnostic.dart' as diag; |
| import 'package:analyzer_testing/package_config_file_builder.dart'; |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| |
| import '../dart/resolution/context_collection_resolution.dart'; |
| |
| main() { |
| defineReflectiveSuite(() { |
| defineReflectiveTests(InvalidWidgetPreviewApplicationTest); |
| }); |
| } |
| |
| @reflectiveTest |
| class InvalidWidgetPreviewApplicationTest extends PubPackageResolutionTest { |
| @override |
| void setUp() { |
| super.setUp(); |
| writeTestPackageConfig(PackageConfigFileBuilder(), flutter: true); |
| } |
| |
| // @Preview cannot be applied to constructors of abstract classes. |
| test_invalidAbstractClassConstructors() async { |
| await assertErrorsInCode( |
| ''' |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter/widget_previews.dart'; |
| |
| abstract class B extends StatelessWidget { |
| @Preview() |
| B(); |
| } |
| ''', |
| [error(diag.invalidWidgetPreviewApplication, 133, 7)], |
| ); |
| } |
| |
| // @Preview application must invoke the `Preview` constructor. |
| test_invalidAnnotationApplication() async { |
| await assertErrorsInCode( |
| ''' |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter/widget_previews.dart'; |
| |
| class B extends StatelessWidget { |
| @Preview |
| B(); |
| } |
| ''', |
| [error(diag.noAnnotationConstructorArguments, 123, 8)], |
| ); |
| } |
| |
| // @Preview cannot be applied to external constructors. |
| test_invalidExternalConstructor() async { |
| await assertErrorsInCode( |
| ''' |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter/widget_previews.dart'; |
| |
| class B extends StatelessWidget { |
| @Preview() |
| external B(); |
| } |
| ''', |
| [error(diag.invalidWidgetPreviewApplication, 124, 7)], |
| ); |
| } |
| |
| // @Preview cannot be applied to external functions. |
| test_invalidExternalStaticFunction() async { |
| await assertErrorsInCode( |
| ''' |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter/widget_previews.dart'; |
| |
| class B extends StatelessWidget { |
| @Preview() |
| external static Widget foo(); |
| } |
| ''', |
| [error(diag.invalidWidgetPreviewApplication, 124, 7)], |
| ); |
| } |
| |
| // @Preview cannot be applied to external top-level functions. |
| test_invalidExternalTopLevelFunction() async { |
| await assertErrorsInCode( |
| ''' |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter/widget_previews.dart'; |
| |
| @Preview() |
| external Widget foo(); |
| ''', |
| [error(diag.invalidWidgetPreviewApplication, 88, 7)], |
| ); |
| } |
| |
| // @Preview cannot be applied to instance members of classes. |
| test_invalidInstanceMethod() async { |
| await assertErrorsInCode( |
| ''' |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter/widget_previews.dart'; |
| |
| class B { |
| @Preview() |
| Widget foo() { |
| return Text('Foo'); |
| } |
| } |
| ''', |
| [error(diag.invalidWidgetPreviewApplication, 100, 7)], |
| ); |
| } |
| |
| // @Preview cannot be applied to nested functions. |
| test_invalidNestedFunction() async { |
| await assertErrorsInCode( |
| ''' |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter/widget_previews.dart'; |
| |
| Widget foo() { |
| @Preview() |
| Widget nested() { |
| return Text('Foo'); |
| } |
| return nested(); |
| } |
| ''', |
| [error(diag.invalidWidgetPreviewApplication, 105, 7)], |
| ); |
| } |
| |
| // @Preview cannot be applied to enum members. |
| test_invalidParentContext_enumMember() async { |
| await assertErrorsInCode( |
| ''' |
| import 'package:flutter/widget_previews.dart'; |
| |
| enum B { |
| a, |
| b, |
| c; |
| |
| @Preview() |
| const B(); |
| } |
| ''', |
| [error(diag.invalidWidgetPreviewApplication, 76, 7)], |
| ); |
| } |
| |
| // @Preview cannot be applied to extension methods. |
| test_invalidParentContext_extensionMethod() async { |
| await assertErrorsInCode( |
| ''' |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter/widget_previews.dart'; |
| |
| class Foo extends StatelessWidget { |
| } |
| |
| extension E on Foo { |
| @Preview() |
| Widget invalidExtensionPreview() => Text('Invalid'); |
| } |
| ''', |
| [error(diag.invalidWidgetPreviewApplication, 150, 7)], |
| ); |
| } |
| |
| // @Preview cannot be applied to extension type members. |
| test_invalidParentContext_extensionTypeMember() async { |
| await assertErrorsInCode( |
| ''' |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter/widget_previews.dart'; |
| |
| class Foo extends StatelessWidget { |
| @override |
| Widget build(BuildContext context) => Text('Foo'); |
| } |
| |
| extension type FooExtensionType(Foo foo) { |
| @Preview() |
| Widget invalidExtensionTypePreview() => Text('Invalid'); |
| } |
| ''', |
| [error(diag.invalidWidgetPreviewApplication, 237, 7)], |
| ); |
| } |
| |
| // @Preview cannot be applied to extension type members. |
| test_invalidParentContext_mixinMember() async { |
| await assertErrorsInCode( |
| ''' |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter/widget_previews.dart'; |
| |
| class Foo extends StatelessWidget { |
| @override |
| Widget build(BuildContext context) => Text('Foo'); |
| } |
| |
| mixin PreviewMixin { |
| @Preview() |
| Widget invalidMixinPreview() => Text('Invalid'); |
| } |
| ''', |
| [error(diag.invalidWidgetPreviewApplication, 215, 7)], |
| ); |
| } |
| |
| // @Preview cannot be applied to constructors of private classes. |
| test_invalidPrivateClass_constructor() async { |
| await assertErrorsInCode( |
| ''' |
| // ignore_for_file: unused_element |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter/widget_previews.dart'; |
| |
| class _B extends StatelessWidget { |
| @Preview() |
| _B(); |
| } |
| ''', |
| [error(diag.invalidWidgetPreviewApplication, 160, 7)], |
| ); |
| } |
| |
| // @Preview cannot be applied to factory constructors of private classes. |
| test_invalidPrivateClass_factoryConstructor() async { |
| await assertErrorsInCode( |
| ''' |
| // ignore_for_file: unused_element |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter/widget_previews.dart'; |
| |
| class _B extends StatelessWidget { |
| @Preview() |
| factory _B.foo() => throw ''; |
| } |
| ''', |
| [error(diag.invalidWidgetPreviewApplication, 160, 7)], |
| ); |
| } |
| |
| // @Preview cannot be applied to instance members of private classes. |
| test_invalidPrivateClass_instanceMember() async { |
| await assertErrorsInCode( |
| ''' |
| // ignore_for_file: unused_element |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter/widget_previews.dart'; |
| |
| class _B extends StatelessWidget { |
| @Preview() |
| static Widget bar() => Text('Bar'); |
| } |
| ''', |
| [error(diag.invalidWidgetPreviewApplication, 160, 7)], |
| ); |
| } |
| |
| // @Preview cannot be applied to private static functions. |
| test_invalidPrivateClassStatic() async { |
| await assertErrorsInCode( |
| ''' |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter/widget_previews.dart'; |
| |
| class B extends StatelessWidget { |
| @Preview() |
| static Widget _foo() { |
| return Text('Foo'); |
| } |
| } |
| ''', |
| [ |
| error(diag.invalidWidgetPreviewApplication, 124, 7), |
| error(diag.unusedElement, 150, 4), |
| ], |
| ); |
| } |
| |
| // @Preview cannot be applied to private factory constructors. |
| test_invalidPrivateConstructor_factory() async { |
| await assertErrorsInCode( |
| ''' |
| // ignore_for_file: unused_element |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter/widget_previews.dart'; |
| |
| class B extends StatelessWidget { |
| @Preview() |
| factory B._foo() => B(); |
| |
| B(); |
| } |
| ''', |
| [error(diag.invalidWidgetPreviewApplication, 159, 7)], |
| ); |
| } |
| |
| // @Preview cannot be applied to private generative constructors. |
| test_invalidPrivateConstructor_generative() async { |
| await assertErrorsInCode( |
| ''' |
| // ignore_for_file: unused_element |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter/widget_previews.dart'; |
| |
| class B extends StatelessWidget { |
| @Preview() |
| B._(); |
| } |
| ''', |
| [error(diag.invalidWidgetPreviewApplication, 159, 7)], |
| ); |
| } |
| |
| // @Preview cannot be applied to private primary constructors. |
| test_invalidPrivateConstructor_primary() async { |
| await assertErrorsInCode( |
| ''' |
| // ignore_for_file: unused_element |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter/widget_previews.dart'; |
| |
| class B._() extends StatelessWidget { |
| @Preview() |
| this; |
| } |
| ''', |
| [error(diag.invalidWidgetPreviewApplication, 163, 7)], |
| ); |
| } |
| |
| // @Preview cannot be applied to private top-level functions. |
| test_invalidPrivateTopLevel() async { |
| await assertErrorsInCode( |
| ''' |
| // ignore_for_file: unused_element |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter/widget_previews.dart'; |
| |
| @Preview() |
| Widget _foo() { |
| return Text('Foo'); |
| } |
| ''', |
| [error(diag.invalidWidgetPreviewApplication, 123, 7)], |
| ); |
| } |
| |
| // Ensure that @Preview can be applied to public factory constructors of |
| // abstract Widget subtypes. |
| test_validAbstractClassFactoryConstructor() async { |
| await assertNoErrorsInCode(''' |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter/widget_previews.dart'; |
| |
| abstract class B extends StatelessWidget { |
| @Preview() |
| factory B() => C(); |
| |
| @Preview() |
| factory B.named() => C.named(); |
| |
| B._(); |
| } |
| |
| class C extends B { |
| C() : super._(); |
| factory C.named() => C(); |
| } |
| '''); |
| } |
| |
| // Ensure that constant instances of @Preview(...) can be applied. |
| test_validAnnotationConstant() async { |
| await assertNoErrorsInCode(''' |
| import 'package:flutter/widget_previews.dart'; |
| import 'package:flutter/widgets.dart'; |
| |
| const myPreview = Preview(name: 'Testing'); |
| |
| @myPreview |
| Widget bar() => Text('Bar'); |
| '''); |
| } |
| |
| // Ensure that @Preview can be applied to public factory constructors of |
| // Widget subtypes, including those with optional parameters. |
| test_validClassFactoryConstructor() async { |
| await assertNoErrorsInCode(''' |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter/widget_previews.dart'; |
| |
| class B extends StatelessWidget { |
| @Preview() |
| factory B.foo({Key? key}) => B._(key: key); |
| |
| @Preview() |
| factory B.bar() => B._(); |
| |
| B._({super.key}); |
| } |
| '''); |
| } |
| |
| // Ensure that @Preview can be applied to public constructors of Widget |
| // subtypes, including those with optional parameters. |
| test_validClassGenerativeConstructor() async { |
| await assertNoErrorsInCode(''' |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter/widget_previews.dart'; |
| |
| class B extends StatelessWidget { |
| @Preview() |
| const B({super.key}); |
| |
| @Preview() |
| B.foo([String? _]); |
| } |
| '''); |
| } |
| |
| // Ensure that @Preview can be applied to public primary constructors of |
| // Widget subtypes, including those with optional parameters. |
| test_validClassPrimaryConstructor() async { |
| await assertNoErrorsInCode(''' |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter/widget_previews.dart'; |
| |
| class B({super.key}) extends StatelessWidget { |
| @Preview() |
| this; |
| } |
| '''); |
| } |
| |
| // Ensure that @Preview can be applied to public static functions that are |
| // defined in public classes and that return Widget or WidgetBuilder. |
| test_validClassStatic() async { |
| await assertNoErrorsInCode(''' |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter/widget_previews.dart'; |
| |
| class B extends StatelessWidget { |
| @Preview() |
| static Widget foo() { |
| return Text('Foo'); |
| } |
| |
| @Preview() |
| static WidgetBuilder bar() { |
| return (BuildContext context) { |
| return Text('Bar'); |
| }; |
| } |
| } |
| '''); |
| } |
| |
| // Ensure that @Preview can be applied to public top-level functions that |
| // return a Widget or WidgetBuilder. |
| test_validTopLevel() async { |
| await assertNoErrorsInCode(''' |
| import 'package:flutter/widget_previews.dart'; |
| import 'package:flutter/widgets.dart'; |
| |
| @Preview(name: 'Widget') |
| Widget foo() => Text('Foo'); |
| |
| @Preview(name: 'WidgetBuilder') |
| WidgetBuilder bar() { |
| return (BuildContext context) { |
| return Text('Bar'); |
| }; |
| } |
| '''); |
| } |
| |
| // Ensure that @Preview can be applied to functions that explicitly return a |
| // subtype of Widget. |
| test_validTopLevel_widgetSubtype() async { |
| await assertNoErrorsInCode(''' |
| import 'package:flutter/widget_previews.dart'; |
| import 'package:flutter/widgets.dart'; |
| |
| typedef MyWidget = Widget; |
| |
| @Preview(name: 'Testing') |
| Text foo() => Text('Foo'); |
| |
| @Preview(name: 'Testing') |
| MyWidget bar() => Text('Bar'); |
| '''); |
| } |
| } |