From efd5a05352e81ec4020e9dfe15d9c96f52a19818 Mon Sep 17 00:00:00 2001 From: Olzhas Suleimen Date: Sun, 20 Apr 2025 23:47:50 +0500 Subject: [PATCH 01/19] Init: ngcompiler, ngdart, ngtest. Signed-off-by: Gavin Zhao --- .../lib/v1/src/angular_compiler/outliner.dart | 2 +- .../lib/v1/src/compiler/compile_metadata.dart | 2 +- .../lib/v1/src/compiler/identifiers.dart | 128 ++++++++++-------- .../view_compiler/compile_element.dart | 2 +- .../update_statement_visitor.dart | 7 +- .../template_compiler/find_components.dart | 2 +- .../lib/src/common/directives/ng_class.dart | 5 +- .../lib/src/common/directives/ng_style.dart | 16 +-- ngdart/lib/src/core/application_ref.dart | 4 +- .../directive_change_detector.dart | 3 +- ngdart/lib/src/core/exception_handler.dart | 8 +- .../lib/src/core/linker/app_view_utils.dart | 10 +- .../src/core/linker/component_factory.dart | 3 +- ngdart/lib/src/core/linker/element_ref.dart | 8 +- .../src/core/linker/style_encapsulation.dart | 29 ++-- .../lib/src/core/linker/view_container.dart | 3 +- ngdart/lib/src/core/linker/view_fragment.dart | 3 +- ngdart/lib/src/core/linker/view_ref.dart | 2 +- .../src/core/linker/views/component_view.dart | 8 +- .../src/core/linker/views/dynamic_view.dart | 3 +- .../src/core/linker/views/embedded_view.dart | 2 +- .../lib/src/core/linker/views/host_view.dart | 3 +- .../src/core/linker/views/render_view.dart | 27 ++-- ngdart/lib/src/core/linker/views/view.dart | 4 +- ngdart/lib/src/devtools.dart | 25 +--- ngdart/lib/src/devtools/inspector.dart | 23 ++-- ngdart/lib/src/meta/directives.dart | 4 +- ngdart/lib/src/runtime/dom_events.dart | 22 +-- ngdart/lib/src/runtime/dom_helpers.dart | 57 ++++---- ngdart/lib/src/runtime/text_binding.dart | 7 +- ngdart/lib/src/security/html_sanitizer.dart | 14 +- ngdart/lib/src/security/safe_inner_html.dart | 13 +- ngdart/lib/src/testability/js_api.dart | 35 ++--- ngdart/lib/src/testability/js_impl.dart | 88 ++++++------ ngdart/lib/src/testability/testability.dart | 9 +- ngdart/lib/src/utilities/unsafe_cast.dart | 1 + ngdart/pubspec.yaml | 2 +- ngtest/lib/src/bootstrap.dart | 2 +- ngtest/lib/src/frontend/bed.dart | 4 +- ngtest/lib/src/frontend/fixture.dart | 7 +- ngtest/pubspec.yaml | 1 + ngtest/test/bootstrap_test.dart | 21 ++- ngtest/test/frontend/bed_error_test.dart | 10 +- ngtest/test/frontend/bed_lifecycle_test.dart | 15 +- ngtest/test/frontend/compatibility_test.dart | 21 +-- tool/package_versions.yaml | 2 +- 46 files changed, 334 insertions(+), 333 deletions(-) diff --git a/ngcompiler/lib/v1/src/angular_compiler/outliner.dart b/ngcompiler/lib/v1/src/angular_compiler/outliner.dart index 0df986d0c5..7c800ed8ec 100644 --- a/ngcompiler/lib/v1/src/angular_compiler/outliner.dart +++ b/ngcompiler/lib/v1/src/angular_compiler/outliner.dart @@ -6,12 +6,12 @@ import 'analyzer.dart'; import 'outliner/collect_type_parameters.dart'; const _angularImports = ''' -import 'dart:html' as _html; import 'package:ngdart/angular.dart' as _ng; import 'package:ngdart/src/core/change_detection/directive_change_detector.dart' as _ng; import 'package:ngdart/src/core/linker/views/component_view.dart' as _ng; import 'package:ngdart/src/core/linker/views/render_view.dart' as _ng; import 'package:ngdart/src/core/linker/views/view.dart' as _ng; +import 'package:web/web.dart' as _html; '''; const _analyzerIgnores = diff --git a/ngcompiler/lib/v1/src/compiler/compile_metadata.dart b/ngcompiler/lib/v1/src/compiler/compile_metadata.dart index 7162d46f23..364ec5a8c2 100644 --- a/ngcompiler/lib/v1/src/compiler/compile_metadata.dart +++ b/ngcompiler/lib/v1/src/compiler/compile_metadata.dart @@ -388,7 +388,7 @@ class CompileQueryMetadata { /// Name of class member on the component to update with query result. final String? propertyName; - /// Whether this is typed `dart:html`'s `Element` (or a sub-type). + /// Whether this is typed `package:web/web.dart`'s `Element` (or a sub-type). final bool isElementType; /// Optional type to read for given match. diff --git a/ngcompiler/lib/v1/src/compiler/identifiers.dart b/ngcompiler/lib/v1/src/compiler/identifiers.dart index c8fc9152fa..49160e1cd3 100644 --- a/ngcompiler/lib/v1/src/compiler/identifiers.dart +++ b/ngcompiler/lib/v1/src/compiler/identifiers.dart @@ -29,6 +29,19 @@ class DevTools { ); } +class JsInterop { + const JsInterop._(); + + static CompileIdentifierMetadata _of(String name) { + return CompileIdentifierMetadata( + name: name, + moduleUrl: 'dart:js_interop', + ); + } + + static final stringToJSString = _of('StringToJSString'); +} + /// A collection of methods for manipulating the DOM from generated code. class DomHelpers { const DomHelpers._(); @@ -274,69 +287,70 @@ class Identifiers { CompileIdentifierMetadata(name: 'NgFor', moduleUrl: _ngForUrl); // Runtime is initialized by output interpreter. Compiler executes in VM and - // can't import dart:html to initialize here. - static var commentNode = - CompileIdentifierMetadata(name: 'Comment', moduleUrl: 'dart:html'); - static var textNode = - CompileIdentifierMetadata(name: 'Text', moduleUrl: 'dart:html'); - static var document = - CompileIdentifierMetadata(name: 'document', moduleUrl: 'dart:html'); + // can't import `package:web/web.dart` to initialize here. + static var commentNode = CompileIdentifierMetadata( + name: 'Comment', moduleUrl: 'package:web/web.dart'); + static var textNode = CompileIdentifierMetadata( + name: 'Text', moduleUrl: 'package:web/web.dart'); + static var document = CompileIdentifierMetadata( + name: 'document', moduleUrl: 'package:web/web.dart'); static final documentFragment = CompileIdentifierMetadata( - name: 'DocumentFragment', moduleUrl: 'dart:html'); - static final element = - CompileIdentifierMetadata(name: 'Element', moduleUrl: 'dart:html'); + name: 'DocumentFragment', moduleUrl: 'package:web/web.dart'); + static final element = CompileIdentifierMetadata( + name: 'Element', moduleUrl: 'package:web/web.dart'); static final elementToken = identifierToken(element); - static final htmlElement = - CompileIdentifierMetadata(name: 'HtmlElement', moduleUrl: 'dart:html'); + static final htmlElement = CompileIdentifierMetadata( + name: 'HTMLElement', moduleUrl: 'package:web/web.dart'); static final htmlElementToken = identifierToken(htmlElement); - static final svgSvgElement = - CompileIdentifierMetadata(name: 'SvgSvgElement', moduleUrl: 'dart:svg'); - static final svgElement = - CompileIdentifierMetadata(name: 'SvgElement', moduleUrl: 'dart:svg'); - static final anchorElement = - CompileIdentifierMetadata(name: 'AnchorElement', moduleUrl: 'dart:html'); - static final divElement = - CompileIdentifierMetadata(name: 'DivElement', moduleUrl: 'dart:html'); - static final areaElement = - CompileIdentifierMetadata(name: 'AreaElement', moduleUrl: 'dart:html'); - static final audioElement = - CompileIdentifierMetadata(name: 'AudioElement', moduleUrl: 'dart:html'); - static final buttonElement = - CompileIdentifierMetadata(name: 'ButtonElement', moduleUrl: 'dart:html'); - static final canvasElement = - CompileIdentifierMetadata(name: 'CanvasElement', moduleUrl: 'dart:html'); - static final formElement = - CompileIdentifierMetadata(name: 'FormElement', moduleUrl: 'dart:html'); - static final iframeElement = - CompileIdentifierMetadata(name: 'IFrameElement', moduleUrl: 'dart:html'); - static final imageElement = - CompileIdentifierMetadata(name: 'ImageElement', moduleUrl: 'dart:html'); - static final inputElement = - CompileIdentifierMetadata(name: 'InputElement', moduleUrl: 'dart:html'); + static final svgSvgElement = CompileIdentifierMetadata( + name: 'SVGSVGElement', moduleUrl: 'package:web/web.dart'); + static final svgElement = CompileIdentifierMetadata( + name: 'SVGElement', moduleUrl: 'package:web/web.dart'); + static final anchorElement = CompileIdentifierMetadata( + name: 'HTMLAnchorElement', moduleUrl: 'package:web/web.dart'); + static final divElement = CompileIdentifierMetadata( + name: 'HTMLDivElement', moduleUrl: 'package:web/web.dart'); + static final areaElement = CompileIdentifierMetadata( + name: 'HTMLAreaElement', moduleUrl: 'package:web/web.dart'); + static final audioElement = CompileIdentifierMetadata( + name: 'HTMLAudioElement', moduleUrl: 'package:web/web.dart'); + static final buttonElement = CompileIdentifierMetadata( + name: 'HTMLButtonElement', moduleUrl: 'package:web/web.dart'); + static final canvasElement = CompileIdentifierMetadata( + name: 'HTMLCanvasElement', moduleUrl: 'package:web/web.dart'); + static final formElement = CompileIdentifierMetadata( + name: 'HTMLFormElement', moduleUrl: 'package:web/web.dart'); + static final iframeElement = CompileIdentifierMetadata( + name: 'HTMLIFrameElement', moduleUrl: 'package:web/web.dart'); + static final imageElement = CompileIdentifierMetadata( + name: 'HTMLImageElement', moduleUrl: 'package:web/web.dart'); + static final inputElement = CompileIdentifierMetadata( + name: 'HTMLInputElement', moduleUrl: 'package:web/web.dart'); static final textareaElement = CompileIdentifierMetadata( - name: 'TextAreaElement', moduleUrl: 'dart:html'); - static final mediaElement = - CompileIdentifierMetadata(name: 'MediaElement', moduleUrl: 'dart:html'); - static final menuElement = - CompileIdentifierMetadata(name: 'MenuElement', moduleUrl: 'dart:html'); - static final nodeTreeSanitizer = CompileIdentifierMetadata( - name: 'NodeTreeSanitizer', moduleUrl: 'dart:html'); - static final optionElement = - CompileIdentifierMetadata(name: 'OptionElement', moduleUrl: 'dart:html'); - static final oListElement = - CompileIdentifierMetadata(name: 'OListElement', moduleUrl: 'dart:html'); - static final selectElement = - CompileIdentifierMetadata(name: 'SelectElement', moduleUrl: 'dart:html'); - static final tableElement = - CompileIdentifierMetadata(name: 'TableElement', moduleUrl: 'dart:html'); + name: 'HTMLTextAreaElement', + moduleUrl: 'package:web/web.dart'); + static final mediaElement = CompileIdentifierMetadata( + name: 'HTMLMediaElement', moduleUrl: 'package:web/web.dart'); + static final menuElement = CompileIdentifierMetadata( + name: 'HTMLMenuElement', moduleUrl: 'package:web/web.dart'); + static final optionElement = CompileIdentifierMetadata( + name: 'HTMLOptionElement', moduleUrl: 'package:web/web.dart'); + static final oListElement = CompileIdentifierMetadata( + name: 'HTMLOListElement', moduleUrl: 'package:web/web.dart'); + static final selectElement = CompileIdentifierMetadata( + name: 'HTMLSelectElement', moduleUrl: 'package:web/web.dart'); + static final tableElement = CompileIdentifierMetadata( + name: 'HTMLTableElement', moduleUrl: 'package:web/web.dart'); static final tableRowElement = CompileIdentifierMetadata( - name: 'TableRowElement', moduleUrl: 'dart:html'); + name: 'HTMLTableRowElement', + moduleUrl: 'package:web/web.dart'); static final tableColElement = CompileIdentifierMetadata( - name: 'TableColElement', moduleUrl: 'dart:html'); - static final uListElement = - CompileIdentifierMetadata(name: 'UListElement', moduleUrl: 'dart:html'); - static final node = - CompileIdentifierMetadata(name: 'Node', moduleUrl: 'dart:html'); + name: 'HTMLTableColElement', + moduleUrl: 'package:web/web.dart'); + static final uListElement = CompileIdentifierMetadata( + name: 'HTMLUListElement', moduleUrl: 'package:web/web.dart'); + static final node = CompileIdentifierMetadata( + name: 'Node', moduleUrl: 'package:web/web.dart'); /// A class used for message internationalization. static final intl = CompileIdentifierMetadata( diff --git a/ngcompiler/lib/v1/src/compiler/view_compiler/compile_element.dart b/ngcompiler/lib/v1/src/compiler/view_compiler/compile_element.dart index 10d3057471..3832eafd72 100644 --- a/ngcompiler/lib/v1/src/compiler/view_compiler/compile_element.dart +++ b/ngcompiler/lib/v1/src/compiler/view_compiler/compile_element.dart @@ -264,7 +264,7 @@ class CompileElement extends CompileNode implements ProviderResolverHost { } else { // If we can't find a valid query type, then we fall back to // ElementRef. HOWEVER, if specifically typed as Element or - // HtmlElement, use that. + // HTMLElement, use that. value = queryWithRead.query.metadata.isElementType ? renderNode.toReadExpr() : elementRef; diff --git a/ngcompiler/lib/v1/src/compiler/view_compiler/update_statement_visitor.dart b/ngcompiler/lib/v1/src/compiler/view_compiler/update_statement_visitor.dart index 506ae6ca10..c80403a476 100644 --- a/ngcompiler/lib/v1/src/compiler/view_compiler/update_statement_visitor.dart +++ b/ngcompiler/lib/v1/src/compiler/view_compiler/update_statement_visitor.dart @@ -1,6 +1,6 @@ import 'package:ngcompiler/v1/src/compiler/compile_metadata.dart'; import 'package:ngcompiler/v1/src/compiler/identifiers.dart' - show DomHelpers, Identifiers, SafeHtmlAdapters; + show DomHelpers, Identifiers, JsInterop, SafeHtmlAdapters; import 'package:ngcompiler/v1/src/compiler/ir/model.dart' as ir; import 'package:ngcompiler/v1/src/compiler/ir/model.dart'; import 'package:ngcompiler/v1/src/compiler/output/output_ast.dart' as o; @@ -124,7 +124,10 @@ class _UpdateStatementsVisitor return o.importExpr(DomHelpers.setProperty).callFn([ renderNode!.toReadExpr(), o.literal(propertyBinding.name), - renderValue!, + o + // TODO(ykmnkmi): math other types + .importExpr(JsInterop.stringToJSString) + .callFn([renderValue!]).prop('toJS'), ]).toStmt(); } diff --git a/ngcompiler/lib/v1/src/source_gen/template_compiler/find_components.dart b/ngcompiler/lib/v1/src/source_gen/template_compiler/find_components.dart index 6830a5c36d..81844b6b2c 100644 --- a/ngcompiler/lib/v1/src/source_gen/template_compiler/find_components.dart +++ b/ngcompiler/lib/v1/src/source_gen/template_compiler/find_components.dart @@ -491,7 +491,7 @@ class _ComponentVisitor } static final _coreIterable = TypeChecker.fromUrl('dart:core#Iterable'); - static final _htmlElement = TypeChecker.fromUrl('dart:html#Element'); + static final _htmlElement = TypeChecker.fromUrl('package:web/src/dom/dom.dart#Element'); CompileQueryMetadata _getQuery( AnnotationInformation annotationInfo, diff --git a/ngdart/lib/src/common/directives/ng_class.dart b/ngdart/lib/src/common/directives/ng_class.dart index 643536e664..d9ca821d9a 100644 --- a/ngdart/lib/src/common/directives/ng_class.dart +++ b/ngdart/lib/src/common/directives/ng_class.dart @@ -1,9 +1,8 @@ -import 'dart:html'; - import 'package:ngdart/src/core/change_detection/differs/default_iterable_differ.dart'; import 'package:ngdart/src/core/change_detection/differs/default_keyvalue_differ.dart'; import 'package:ngdart/src/meta.dart'; import 'package:ngdart/src/utilities.dart'; +import 'package:web/web.dart'; /// The [NgClass] directive conditionally adds and removes CSS classes on an /// HTML element based on an expression's evaluation result. @@ -173,7 +172,7 @@ class NgClass implements DoCheck, OnDestroy { className = className.trim(); if (className.isEmpty) return; var el = _ngEl; - var classList = el.classes; + var classList = el.classList; if (className.contains(' ')) { var classes = className.split(_separator); for (var i = 0, len = classes.length; i < len; i++) { diff --git a/ngdart/lib/src/common/directives/ng_style.dart b/ngdart/lib/src/common/directives/ng_style.dart index 193ec5bf10..15c5ce0343 100644 --- a/ngdart/lib/src/common/directives/ng_style.dart +++ b/ngdart/lib/src/common/directives/ng_style.dart @@ -1,9 +1,7 @@ -import 'dart:html'; - +import 'package:ngdart/src/core/change_detection/differs/default_keyvalue_differ.dart'; import 'package:ngdart/src/meta.dart'; -import 'package:ngdart/src/utilities.dart'; - -import '../../core/change_detection/differs/default_keyvalue_differ.dart'; +import 'package:ngdart/src/utilities/unsafe_cast.dart'; +import 'package:web/web.dart'; /// The `NgStyle` directive changes an element's style based on the bound style /// expression: @@ -85,9 +83,9 @@ class NgStyle implements DoCheck { } void _setProperty(KeyValueChangeRecord record) { - _ngElement.style.setProperty( - unsafeCast(record.key), - unsafeCast(record.currentValue), - ); + // HTMLElement, SVGElement and MathMLElement have same `style` property. + (_ngElement as HTMLElement) + .style + .setProperty(unsafeCast(record.key), unsafeCast(record.currentValue)); } } diff --git a/ngdart/lib/src/core/application_ref.dart b/ngdart/lib/src/core/application_ref.dart index a8fd36da7d..636c26d861 100644 --- a/ngdart/lib/src/core/application_ref.dart +++ b/ngdart/lib/src/core/application_ref.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:html'; import 'package:meta/dart2js.dart' as dart2js; import 'package:ngdart/src/core/exception_handler.dart'; @@ -7,6 +6,7 @@ import 'package:ngdart/src/devtools.dart'; import 'package:ngdart/src/di/injector.dart'; import 'package:ngdart/src/testability.dart'; import 'package:ngdart/src/utilities.dart'; +import 'package:web/web.dart'; import 'change_detection/host.dart'; import 'linker/component_factory.dart' show ComponentRef, ComponentFactory; @@ -70,7 +70,7 @@ class ApplicationRef extends ChangeDetectionHost { ) { return unsafeCast(run(() { final component = componentFactory.create(_injector); - final existing = querySelector(componentFactory.selector); + final existing = document.querySelector(componentFactory.selector); Element? replacement; if (existing != null) { final newElement = component.location; diff --git a/ngdart/lib/src/core/change_detection/directive_change_detector.dart b/ngdart/lib/src/core/change_detection/directive_change_detector.dart index 51c1065831..7742deaed4 100644 --- a/ngdart/lib/src/core/change_detection/directive_change_detector.dart +++ b/ngdart/lib/src/core/change_detection/directive_change_detector.dart @@ -1,6 +1,5 @@ -import 'dart:html'; - import 'package:ngdart/src/core/linker/views/render_view.dart'; +import 'package:web/web.dart'; /// Base class for helpers generated for some classes annotated with @Directive. /// diff --git a/ngdart/lib/src/core/exception_handler.dart b/ngdart/lib/src/core/exception_handler.dart index 918ac544cd..8c73bd25b3 100644 --- a/ngdart/lib/src/core/exception_handler.dart +++ b/ngdart/lib/src/core/exception_handler.dart @@ -1,4 +1,6 @@ -import 'dart:html'; +import 'dart:js_interop'; + +import 'package:web/web.dart'; /// Provides a hook for receiving unhandled errors/exceptions. /// @@ -64,10 +66,10 @@ class ExceptionHandler { Object? stackTrace, @Deprecated('No longer supported. Remove this argument.') String? reason, ]) { - window.console.error(ExceptionHandler.exceptionToString( + console.error(ExceptionHandler.exceptionToString( exception, stackTrace, reason, - )); + ).toJS); } } diff --git a/ngdart/lib/src/core/linker/app_view_utils.dart b/ngdart/lib/src/core/linker/app_view_utils.dart index 4258326ea5..bac4282ade 100644 --- a/ngdart/lib/src/core/linker/app_view_utils.dart +++ b/ngdart/lib/src/core/linker/app_view_utils.dart @@ -1,7 +1,8 @@ -import 'dart:html' show DocumentFragment, NodeTreeSanitizer; +import 'dart:js_interop'; import 'package:ngdart/src/core/application_tokens.dart' as tokens show appId; import 'package:ngdart/src/runtime/dom_events.dart' show EventManager; +import 'package:web/web.dart'; /// Application wide view utilities. late AppViewUtils appViewUtils; @@ -20,8 +21,7 @@ class AppViewUtils { /// Creates a document fragment from [trustedHtml]. DocumentFragment createTrustedHtml(String trustedHtml) { - return DocumentFragment.html( - trustedHtml, - treeSanitizer: NodeTreeSanitizer.trusted, - ); + final template = HTMLTemplateElement(); + template.innerHTML = trustedHtml.toJS; + return template.content; } diff --git a/ngdart/lib/src/core/linker/component_factory.dart b/ngdart/lib/src/core/linker/component_factory.dart index df43db9f59..61a0491053 100644 --- a/ngdart/lib/src/core/linker/component_factory.dart +++ b/ngdart/lib/src/core/linker/component_factory.dart @@ -1,11 +1,10 @@ -import 'dart:html'; - import 'package:meta/meta.dart'; import 'package:ngdart/src/core/change_detection/change_detector_ref.dart'; import 'package:ngdart/src/core/zone/ng_zone.dart'; import 'package:ngdart/src/di/injector.dart'; import 'package:ngdart/src/meta.dart'; import 'package:ngdart/src/utilities.dart'; +import 'package:web/web.dart'; import 'view_ref.dart' show ViewRef; import 'views/host_view.dart'; diff --git a/ngdart/lib/src/core/linker/element_ref.dart b/ngdart/lib/src/core/linker/element_ref.dart index 4fe3eacf81..ef7c82b03c 100644 --- a/ngdart/lib/src/core/linker/element_ref.dart +++ b/ngdart/lib/src/core/linker/element_ref.dart @@ -5,9 +5,10 @@ /// /// **DEPRECATED**: A wrapper around a native DOM element inside of a View. /// -/// Inject `Element` or `HtmlElement` from `dart:html` instead; this will be +/// Inject `Element` or `HTMLElement` from `dart:html` instead; this will be /// removed in a future version of AngularDart, and has unnecessary overhead. -@Deprecated('Inject or reference dart:html Element or HtmlElement instead') +@Deprecated( + 'Inject or reference package:web/web.dart Element or HTMLElement instead') class ElementRef { final dynamic nativeElement; @@ -15,6 +16,7 @@ class ElementRef { // // Then it is upcasted to dynamic for the public API to be non-breaking. // ignore: prefer_initializing_formals - @Deprecated('Inject or reference dart:html Element or HtmlElement instead') + @Deprecated( + 'Inject or reference package:web/web.dart Element or HTMLElement instead') const ElementRef(this.nativeElement); } diff --git a/ngdart/lib/src/core/linker/style_encapsulation.dart b/ngdart/lib/src/core/linker/style_encapsulation.dart index 9f2fa41aaf..7aed15df57 100644 --- a/ngdart/lib/src/core/linker/style_encapsulation.dart +++ b/ngdart/lib/src/core/linker/style_encapsulation.dart @@ -1,9 +1,8 @@ -import 'dart:html'; - import 'package:meta/dart2js.dart' as dart2js; import 'package:ngdart/src/core/linker/app_view_utils.dart'; import 'package:ngdart/src/runtime/dom_helpers.dart'; import 'package:ngdart/src/utilities.dart'; +import 'package:web/web.dart'; /// Clears all component styles from the DOM. /// @@ -119,8 +118,8 @@ class ComponentStyles { updateClassBindingNonHtml(element, _contentPrefix, true); } - /// An optimized variant of [addShimClass] for [HtmlElement]s. - void addContentShimClassHtmlElement(HtmlElement element) { + /// An optimized variant of [addShimClass] for [HTMLElement]s. + void addContentShimClassHtmlElement(HTMLElement element) { updateClassBinding(element, _contentPrefix, true); } @@ -129,8 +128,8 @@ class ComponentStyles { updateClassBindingNonHtml(element, _hostPrefix, true); } - /// An optimized variant of [addHostShimClass] for [HtmlElement]s. - void addHostShimClassHtmlElement(HtmlElement element) { + /// An optimized variant of [addHostShimClass] for [HTMLElement]s. + void addHostShimClassHtmlElement(HTMLElement element) { updateClassBinding(element, _hostPrefix, true); } @@ -140,8 +139,8 @@ class ComponentStyles { updateAttribute(element, 'class', '$newClass $_contentPrefix'); } - /// An optimized variant of [updateChildClass] for [HtmlElement]s. - void updateChildClassHtmlElement(HtmlElement element, String newClass) { + /// An optimized variant of [updateChildClass] for [HTMLElement]s. + void updateChildClassHtmlElement(HTMLElement element, String newClass) { element.className = '$newClass $_contentPrefix'; } @@ -151,9 +150,9 @@ class ComponentStyles { updateAttribute(element, 'class', '$newClass $_hostPrefix'); } - /// An optimized variant of [updateChildClassForHost] for [HtmlElement]s. + /// An optimized variant of [updateChildClassForHost] for [HTMLElement]s. void updateChildClassForHostHtmlElement( - HtmlElement element, + HTMLElement element, String newClass, ) { element.className = '$newClass $_hostPrefix'; @@ -167,7 +166,7 @@ class ComponentStyles { target.add('/* From: $_componentUrl*/'); } final styles = _flattenStyles(_styles, target, _componentId).join(); - final styleElement = StyleElement()..text = styles; + final styleElement = HTMLStyleElement()..textContent = styles; if (isDevMode) { // Remove style element from the DOM on hot restart. debugOnClear(() { @@ -190,7 +189,7 @@ class _UnscopedComponentStyles extends ComponentStyles { } @override - void addContentShimClassHtmlElement(HtmlElement element) { + void addContentShimClassHtmlElement(HTMLElement element) { // Intentionally left blank; unscoped syles do not apply shim classes. } @@ -200,7 +199,7 @@ class _UnscopedComponentStyles extends ComponentStyles { } @override - void addHostShimClassHtmlElement(HtmlElement element) { + void addHostShimClassHtmlElement(HTMLElement element) { // Intentionally left blank; unscoped syles do not apply shim classes. } @@ -212,7 +211,7 @@ class _UnscopedComponentStyles extends ComponentStyles { } @override - void updateChildClassHtmlElement(HtmlElement element, String newClass) { + void updateChildClassHtmlElement(HTMLElement element, String newClass) { element.className = newClass; } @@ -225,7 +224,7 @@ class _UnscopedComponentStyles extends ComponentStyles { @override void updateChildClassForHostHtmlElement( - HtmlElement element, + HTMLElement element, String newClass, ) { // Straight applies the class without any prefixing. diff --git a/ngdart/lib/src/core/linker/view_container.dart b/ngdart/lib/src/core/linker/view_container.dart index f1f11280da..fe355e3fc2 100644 --- a/ngdart/lib/src/core/linker/view_container.dart +++ b/ngdart/lib/src/core/linker/view_container.dart @@ -1,8 +1,7 @@ -import 'dart:html'; - import 'package:meta/meta.dart'; import 'package:ngdart/src/di/injector.dart' show Injector; import 'package:ngdart/src/utilities.dart'; +import 'package:web/web.dart'; import 'component_factory.dart' show ComponentFactory, ComponentRef; import 'component_loader.dart'; diff --git a/ngdart/lib/src/core/linker/view_fragment.dart b/ngdart/lib/src/core/linker/view_fragment.dart index 9a52fe19a2..410aa14dc5 100644 --- a/ngdart/lib/src/core/linker/view_fragment.dart +++ b/ngdart/lib/src/core/linker/view_fragment.dart @@ -1,7 +1,6 @@ -import 'dart:html'; - import 'package:meta/dart2js.dart' as dart2js; import 'package:ngdart/src/utilities.dart'; +import 'package:web/web.dart'; import 'view_container.dart'; diff --git a/ngdart/lib/src/core/linker/view_ref.dart b/ngdart/lib/src/core/linker/view_ref.dart index a0d04317c3..5d10e69cdd 100644 --- a/ngdart/lib/src/core/linker/view_ref.dart +++ b/ngdart/lib/src/core/linker/view_ref.dart @@ -1,4 +1,4 @@ -import 'dart:html'; +import 'package:web/web.dart'; /// An Angular view that can be created and destroyed dynamically. /// diff --git a/ngdart/lib/src/core/linker/views/component_view.dart b/ngdart/lib/src/core/linker/views/component_view.dart index bd9d050643..0286636bd4 100644 --- a/ngdart/lib/src/core/linker/views/component_view.dart +++ b/ngdart/lib/src/core/linker/views/component_view.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:html'; import 'package:meta/dart2js.dart' as dart2js; import 'package:meta/meta.dart'; @@ -8,6 +7,7 @@ import 'package:ngdart/src/core/linker/style_encapsulation.dart'; import 'package:ngdart/src/devtools.dart'; import 'package:ngdart/src/meta.dart'; import 'package:ngdart/src/utilities.dart'; +import 'package:web/web.dart'; import 'render_view.dart'; import 'view.dart'; @@ -44,7 +44,7 @@ abstract class ComponentView extends RenderView { late final ComponentStyles componentStyles; /// The root element of this component, created from its selector. - late final HtmlElement rootElement; + late final HTMLElement rootElement; final _ComponentViewData _data; @@ -110,7 +110,7 @@ abstract class ComponentView extends RenderView { /// requires less code to assign the return value of a function that's going /// to be called anyways, than to generate an extra statement to load a field. @dart2js.noInline - HtmlElement initViewRoot() { + HTMLElement initViewRoot() { final hostElement = rootElement; componentStyles.addHostShimClassHtmlElement(hostElement); return hostElement; @@ -215,7 +215,7 @@ abstract class ComponentView extends RenderView { @dart2js.noInline @override - void updateChildClass(HtmlElement element, String newClass) { + void updateChildClass(HTMLElement element, String newClass) { if (identical(element, rootElement)) { componentStyles.updateChildClassForHostHtmlElement(element, newClass); final parent = parentView; diff --git a/ngdart/lib/src/core/linker/views/dynamic_view.dart b/ngdart/lib/src/core/linker/views/dynamic_view.dart index fdadf47784..da08f96fd8 100644 --- a/ngdart/lib/src/core/linker/views/dynamic_view.dart +++ b/ngdart/lib/src/core/linker/views/dynamic_view.dart @@ -1,8 +1,7 @@ -import 'dart:html'; - import 'package:ngdart/src/core/linker/view_container.dart'; import 'package:ngdart/src/core/linker/view_fragment.dart'; import 'package:ngdart/src/core/linker/view_ref.dart'; +import 'package:web/web.dart'; import 'view.dart'; diff --git a/ngdart/lib/src/core/linker/views/embedded_view.dart b/ngdart/lib/src/core/linker/views/embedded_view.dart index 7809947be3..7e9ffa9d15 100644 --- a/ngdart/lib/src/core/linker/views/embedded_view.dart +++ b/ngdart/lib/src/core/linker/views/embedded_view.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:html'; import 'package:meta/dart2js.dart' as dart2js; import 'package:meta/meta.dart'; @@ -11,6 +10,7 @@ import 'package:ngdart/src/core/linker/view_ref.dart'; import 'package:ngdart/src/meta.dart'; import 'package:ngdart/src/runtime/dom_helpers.dart'; import 'package:ngdart/src/utilities.dart'; +import 'package:web/web.dart'; import 'dynamic_view.dart'; import 'render_view.dart'; diff --git a/ngdart/lib/src/core/linker/views/host_view.dart b/ngdart/lib/src/core/linker/views/host_view.dart index 5f481ceb5a..8f8b12a5ba 100644 --- a/ngdart/lib/src/core/linker/views/host_view.dart +++ b/ngdart/lib/src/core/linker/views/host_view.dart @@ -1,5 +1,3 @@ -import 'dart:html'; - import 'package:meta/dart2js.dart' as dart2js; import 'package:meta/meta.dart'; import 'package:ngdart/src/core/change_detection/host.dart'; @@ -10,6 +8,7 @@ import 'package:ngdart/src/di/injector.dart'; import 'package:ngdart/src/meta.dart'; import 'package:ngdart/src/runtime/dom_helpers.dart'; import 'package:ngdart/src/utilities.dart'; +import 'package:web/web.dart'; import 'component_view.dart'; import 'dynamic_view.dart'; diff --git a/ngdart/lib/src/core/linker/views/render_view.dart b/ngdart/lib/src/core/linker/views/render_view.dart index 40e2036a71..ef068ff317 100644 --- a/ngdart/lib/src/core/linker/views/render_view.dart +++ b/ngdart/lib/src/core/linker/views/render_view.dart @@ -1,5 +1,5 @@ import 'dart:async'; -import 'dart:html'; +import 'dart:js_interop'; import 'package:meta/dart2js.dart' as dart2js; import 'package:ngdart/src/core/linker/app_view_utils.dart'; @@ -8,6 +8,7 @@ import 'package:ngdart/src/core/linker/view_container.dart'; import 'package:ngdart/src/core/linker/view_fragment.dart'; import 'package:ngdart/src/runtime/dom_helpers.dart'; import 'package:ngdart/src/utilities.dart'; +import 'package:web/web.dart'; import 'view.dart'; @@ -128,11 +129,11 @@ abstract class RenderView extends View { /// * Calls [markForCheck] on this view to ensure it gets change detected /// during the next change detection cycle, in case it uses a non-default /// change detection strategy. - void Function(E) eventHandler0(void Function() handler) { - return (E event) { + JSFunction eventHandler0(void Function() handler) { + return (Event event) { markForCheck(); appViewUtils.eventManager.zone.runGuarded(handler); - }; + }.toJS; } /// The same as [eventHandler0], but [handler] is passed an event parameter. @@ -149,24 +150,20 @@ abstract class RenderView extends View { /// of the event listener is a subclass of [Event]. The [Event] passed in from /// [EventTarget.addEventListener] can then be safely coerced back to its /// known type. - void Function(E) eventHandler1(void Function(F) handler) { - assert( - E == Null || F != Null, - "Event handler '$handler' isn't assignable to expected type " - "'($E) => void'"); + JSFunction eventHandler1(void Function(E event) handler) { return (E event) { markForCheck(); appViewUtils.eventManager.zone.runGuarded( - () => handler(unsafeCast(event)), + () => handler(event), ); - }; + }.toJS; } // Styling ------------------------------------------------------------------- - /// Equivalent to [addShimE], but optimized for [HtmlElement]. + /// Equivalent to [addShimE], but optimized for [HTMLElement]. @dart2js.tryInline - void addShimC(HtmlElement element) { + void addShimC(HTMLElement element) { componentStyles.addContentShimClassHtmlElement(element); } @@ -176,7 +173,7 @@ abstract class RenderView extends View { /// shim class is needed for any styles to match [element]. /// /// This should only be used for SVG or custom elements. For a plain - /// [HtmlElement], use [addShimC] instead. + /// [HTMLElement], use [addShimC] instead. @dart2js.tryInline void addShimE(Element element) { componentStyles.addContentShimClass(element); @@ -189,7 +186,7 @@ abstract class RenderView extends View { /// /// For example, through the `[class]="..."` or `[attr.class]="..."` syntax. @dart2js.noInline - void updateChildClass(HtmlElement element, String newClass) { + void updateChildClass(HTMLElement element, String newClass) { componentStyles.updateChildClassHtmlElement(element, newClass); } diff --git a/ngdart/lib/src/core/linker/views/view.dart b/ngdart/lib/src/core/linker/views/view.dart index b5898426d8..dd26fc4620 100644 --- a/ngdart/lib/src/core/linker/views/view.dart +++ b/ngdart/lib/src/core/linker/views/view.dart @@ -1,4 +1,4 @@ -import 'dart:html' show Element; +import 'dart:js_interop'; import 'package:meta/dart2js.dart' as dart2js; import 'package:meta/meta.dart'; @@ -124,7 +124,7 @@ abstract class View implements ChangeDetectorRef { @override void markChildForCheck(Object child) { - assert(child is! Element, 'Expected a component instance'); + assert(child is! JSAny, 'Expected a component instance'); queryChangeDetectorRefs[child]?.markForCheck(); } diff --git a/ngdart/lib/src/devtools.dart b/ngdart/lib/src/devtools.dart index 12e991ebfd..aa97095130 100644 --- a/ngdart/lib/src/devtools.dart +++ b/ngdart/lib/src/devtools.dart @@ -1,9 +1,6 @@ -@JS() -library; +import 'dart:js_interop'; -import 'dart:html' as html; - -import 'package:js/js.dart'; +import 'package:web/web.dart'; import 'devtools/inspector.dart'; import 'utilities.dart'; @@ -23,12 +20,8 @@ bool _isDevToolsEnabled = false; void enableDevTools() { if (isDevMode) { _isDevToolsEnabled = true; - _getComponentElement = allowInterop( - Inspector.instance.getComponentElement, - ); - _getComponentIdForNode = allowInterop( - Inspector.instance.getComponentIdForNode, - ); + _getComponentElement = Inspector.instance.getComponentElement.toJS; + _getComponentIdForNode = Inspector.instance.getComponentIdForNode.toJS; } } @@ -36,7 +29,7 @@ void enableDevTools() { /// /// This method should be used to register elements that are not contained by /// the app's root component. -void registerContentRoot(html.Element element) { +void registerContentRoot(Element element) { if (isDevToolsEnabled) { Inspector.instance.registerContentRoot(element); } @@ -44,11 +37,7 @@ void registerContentRoot(html.Element element) { /// Specifies a function to look up an element by component ID in JavaScript. @JS('getAngularComponentElement') -external set _getComponentElement( - html.HtmlElement Function(int) implementation, -); +external set _getComponentElement(JSFunction implementation); @JS('getAngularComponentIdForNode') -external set _getComponentIdForNode( - void Function(html.Node, String) implementation, -); +external set _getComponentIdForNode(JSFunction implementation); diff --git a/ngdart/lib/src/devtools/inspector.dart b/ngdart/lib/src/devtools/inspector.dart index 69a7e0edf9..860260ffa9 100644 --- a/ngdart/lib/src/devtools/inspector.dart +++ b/ngdart/lib/src/devtools/inspector.dart @@ -1,12 +1,13 @@ import 'dart:async'; import 'dart:convert' show json; import 'dart:developer'; -import 'dart:html'; +import 'dart:js_interop'; import 'package:built_collection/built_collection.dart'; import 'package:built_value/serializer.dart'; import 'package:meta/meta.dart'; import 'package:stream_transform/stream_transform.dart'; +import 'package:web/web.dart'; import '../core/application_ref.dart'; import '../core/linker/views/component_view.dart'; @@ -69,10 +70,11 @@ class Inspector { /// inspecting another. void inspect(ApplicationRef applicationRef) { if (_applicationRef != null) { - window.console.error(''' + console.error(''' AngularDart DevTools does not yet support apps with multiple runApp() invocations. Please contact angulardart-eng@ if you encounter this error. -'''); +''' + .toJS); return; } @@ -228,7 +230,7 @@ invocations. Please contact angulardart-eng@ if you encounter this error. } /// Returns the root element of the component for [id]. - HtmlElement getComponentElement(int id) { + HTMLElement getComponentElement(int id) { final componentView = _referenceCounter.toObject(id) as ComponentView; return componentView.rootElement; @@ -247,7 +249,7 @@ invocations. Please contact angulardart-eng@ if you encounter this error. if (componentView != null) { return _referenceCounter.toId(componentView, groupName); } - current = current.parent; + current = current.parentElement; } return -1; } @@ -280,7 +282,10 @@ invocations. Please contact angulardart-eng@ if you encounter this error. List> getComponents(String groupName) { final json = >[]; for (final element in _contentRoots) { - final treeWalker = TreeWalker(element, NodeFilter.SHOW_ELEMENT); + final treeWalker = document.createTreeWalker( + element, + /* NodeFilter.SHOW_ELEMENT */ 0x1, + ); _collectJson(treeWalker, groupName, json); } return json; @@ -293,10 +298,12 @@ invocations. Please contact angulardart-eng@ if you encounter this error. @visibleForTesting BuiltList getNodes(String groupName) { return BuiltList.build((b) { + final whatToShow = /* NodeFilter.SHOW_ELEMENT */ + 0x1 | /* NodeFilter.SHOW_COMMENT */ 0x80; + for (final element in _contentRoots) { // Structural directives can be anchored on comments. - final whatToShow = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT; - final treeWalker = TreeWalker(element, whatToShow); + final treeWalker = document.createTreeWalker(element, whatToShow); _collectNodes(treeWalker, groupName, b); } }); diff --git a/ngdart/lib/src/meta/directives.dart b/ngdart/lib/src/meta/directives.dart index de350e6552..6b9c544de9 100644 --- a/ngdart/lib/src/meta/directives.dart +++ b/ngdart/lib/src/meta/directives.dart @@ -10,7 +10,7 @@ import 'visibility.dart'; /// /// /// ```dart -/// import 'dart:html'; +/// import 'package:web/web.dart'; /// /// import 'package:ngdart/angular.dart'; /// @@ -270,7 +270,7 @@ class Pipe { /// /// > **NOTE**: `@Attribute` is not affected by any updates to attributes to the /// > host element (including the `[attr.*]` template syntax, or imperative -/// > updates to the DOM using `dart:html`). +/// > updates to the DOM using `package:web/web.dart`). /// /// ### Example /// diff --git a/ngdart/lib/src/runtime/dom_events.dart b/ngdart/lib/src/runtime/dom_events.dart index 9bfc27de4d..afd96a51c8 100644 --- a/ngdart/lib/src/runtime/dom_events.dart +++ b/ngdart/lib/src/runtime/dom_events.dart @@ -1,6 +1,8 @@ -import 'dart:html'; +import 'dart:js_interop'; import 'package:ngdart/src/core/zone/ng_zone.dart'; +import 'package:ngdart/src/utilities.dart'; +import 'package:web/web.dart'; /// Provides a runtime implementation for "native" DOM events on elements. class EventManager { @@ -18,7 +20,7 @@ class EventManager { void addEventListener( Element element, String name, - void Function(Object) callback, + void Function(Event) callback, ) { if (_keyEvents.supports(name)) { // Run the actual DOM event (i.e. "keydown" or "keyup") outside of the @@ -33,7 +35,7 @@ class EventManager { // If the view compiler knows that a given event is a DOM event (i.e. // "click"), it will never be called into EventManager. But of course the // browser APIs change, so this is the final fallback. - element.addEventListener(name, callback); + element.addEventListener(name, callback.toJS); } } @@ -75,7 +77,7 @@ class _KeyEventsHandler { void addEventListener( Element element, String name, - void Function(Object) callback, + void Function(Event) callback, ) { assert(_supports(name), 'Should never be called before "supports".'); final parsed = _cache[name]; @@ -85,11 +87,13 @@ class _KeyEventsHandler { return; } - element.addEventListener(parsed.domEventName, (event) { - if (event is KeyboardEvent && parsed.matches(event)) { - callback(event); - } - }); + element.addEventListener( + parsed.domEventName, + (Event event) { + if (event.isA() && parsed.matches(unsafeCast(event))) { + callback(event); + } + }.toJS); } static _ParsedEvent? _parse(String name) { diff --git a/ngdart/lib/src/runtime/dom_helpers.dart b/ngdart/lib/src/runtime/dom_helpers.dart index 358fd35587..5d97b55efe 100644 --- a/ngdart/lib/src/runtime/dom_helpers.dart +++ b/ngdart/lib/src/runtime/dom_helpers.dart @@ -1,15 +1,14 @@ /// This library is considered separate from rest of `runtime.dart`, as it -/// imports `dart:html` and `runtime.dart` is currently used on libraries +/// imports `web` package and `runtime.dart` is currently used on libraries /// that expect to only run on the command-line VM. -@JS() library; -import 'dart:html' hide document; +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; -import 'package:js/js.dart'; -import 'package:js/js_util.dart' as js; import 'package:meta/dart2js.dart' as dart2js; import 'package:ngdart/src/utilities.dart'; +import 'package:web/web.dart'; /// https://developer.mozilla.org/en-US/docs/Web/API/Document/createTextNode Text _createTextNode(String text) => Text(text); @@ -42,27 +41,27 @@ var domRootRendererIsDirty = false; /// /// For [element]s not guaranteed to be HTML, see [updateClassBindingNonHtml]. @dart2js.noInline -void updateClassBinding(HtmlElement element, String className, bool isAdd) { +void updateClassBinding(HTMLElement element, String className, bool isAdd) { if (isAdd) { - element.classes.add(className); + element.classList.add(className); } else { - element.classes.remove(className); + element.classList.remove(className); } } /// Similar to [updateClassBinding], for an [element] not guaranteed to be HTML. /// -/// For example, using [Element.tag] to create a custom element will not be -/// recognized as a built-in HTML element, or for SVG elements created by the +/// For example, using [document.createElement] to create a custom element will +/// not be recognized as a built-in HTML element, or for SVG elements created by the /// template. /// /// Dart2JS emits slightly more optimized cost in [updateClassBinding]. @dart2js.noInline void updateClassBindingNonHtml(Element element, String className, bool isAdd) { if (isAdd) { - element.classes.add(className); + element.classList.add(className); } else { - element.classes.remove(className); + element.classList.remove(className); } } @@ -124,9 +123,9 @@ void setAttribute( void setProperty( Element element, String property, - Object? value, + JSAny? value, ) { - js.setProperty(element, property, value); + element[property] = value; } /// Creates a [Text] node with the provided [contents]. @@ -179,7 +178,7 @@ Text createText(String contents) { /// This is an optimization to reduce code size for a common operation. @dart2js.noInline Text appendText(Node parent, String text) { - return unsafeCast(parent.append(createText(text))); + return unsafeCast(parent.appendChild(createText(text))); } /// Returns a new [Comment] node with empty contents. @@ -193,23 +192,23 @@ Comment createAnchor() => _createComment(); /// This is an optimization to reduce code size for a common operation. @dart2js.noInline Comment appendAnchor(Node parent) { - return unsafeCast(parent.append(_createComment())); + return unsafeCast(parent.appendChild(_createComment())); } /// Appends and returns a new empty [DivElement] to a [parent] node. /// /// This is an optimization to reduce code size for a common operation. @dart2js.noInline -DivElement appendDiv(Document doc, Node parent) { - return unsafeCast(parent.append(doc.createElement('div'))); +HTMLDivElement appendDiv(Document doc, Node parent) { + return unsafeCast(parent.appendChild(doc.createElement('div'))); } -/// Appends and returns a new empty [SpanElement] to a [parent] node. +/// Appends and returns a new empty [HTMLSpanElement] to a [parent] node. /// /// This is an optimization to reduce code size for a common operation. @dart2js.noInline -SpanElement appendSpan(Document doc, Node parent) { - return unsafeCast(parent.append(doc.createElement('span'))); +HTMLSpanElement appendSpan(Document doc, Node parent) { + return unsafeCast(parent.appendChild(doc.createElement('span'))); } /// Appends and returns a new empty [Element] to a [parent] node. @@ -224,17 +223,13 @@ T appendElement( String tagName, ) { // allows the pattern: - // HtmlElement e = appendElement(doc, parent, 'foo') + // HTMLElement e = appendElement(doc, parent, 'foo') // // ... without gratituous use of unsafeCast or casts in general. - return unsafeCast(parent.append(doc.createElement(tagName))); + return unsafeCast(parent.appendChild(doc.createElement(tagName))); } /// Inserts [nodes] into the DOM before [sibling]. -/// -/// This intentionally does not use [Node.insertAllBefore], which is slower due -/// to extra type and runtime checks that are not necessary for our generated -/// code. @dart2js.noInline void insertNodesBefore(List nodes, Node parent, Node sibling) { for (var i = 0, l = nodes.length; i < l; i++) { @@ -246,15 +241,15 @@ void insertNodesBefore(List nodes, Node parent, Node sibling) { @dart2js.noInline void appendNodes(List nodes, Node parent) { for (var i = 0, l = nodes.length; i < l; i++) { - parent.append(nodes[i]); + parent.appendChild(nodes[i]); } } /// Removes [nodes] from the DOM. @dart2js.noInline void removeNodes(List nodes) { - for (var i = 0, l = nodes.length; i < l; i++) { - nodes[i].remove(); + for (var i = 0, node = nodes[i], l = nodes.length; i < l; node = nodes[++i]) { + node.parentNode?.removeChild(node); } } @@ -267,7 +262,7 @@ void insertNodesAsSibling(List nodes, Node sibling) { if (nodes.isEmpty || parentOfSibling == null) { return; } - final nextSibling = sibling.nextNode; + final nextSibling = sibling.nextSibling; if (nextSibling == null) { appendNodes(nodes, parentOfSibling); } else { diff --git a/ngdart/lib/src/runtime/text_binding.dart b/ngdart/lib/src/runtime/text_binding.dart index ece51b7083..e80996a9e3 100644 --- a/ngdart/lib/src/runtime/text_binding.dart +++ b/ngdart/lib/src/runtime/text_binding.dart @@ -1,7 +1,6 @@ -import 'dart:html'; - import 'package:meta/dart2js.dart' as dart2js; import 'package:ngdart/src/runtime/check_binding.dart'; +import 'package:web/web.dart'; import 'interpolate.dart'; @@ -25,7 +24,7 @@ class TextBinding { /// Update the [Text] node if [newValue] differs from the previous value. void updateText(String newValue) { if (checkBinding(_currentValue, newValue)) { - element.text = newValue; + element.textContent = newValue; _currentValue = newValue; } } @@ -34,7 +33,7 @@ class TextBinding { /// and differs from the previous value. void updateTextWithPrimitive(Object? newValue) { if (checkBinding(_currentValue, newValue)) { - element.text = interpolate0(newValue); + element.textContent = interpolate0(newValue); _currentValue = newValue; } } diff --git a/ngdart/lib/src/security/html_sanitizer.dart b/ngdart/lib/src/security/html_sanitizer.dart index 336bbfb539..d29890715b 100644 --- a/ngdart/lib/src/security/html_sanitizer.dart +++ b/ngdart/lib/src/security/html_sanitizer.dart @@ -1,15 +1,11 @@ -import 'dart:html'; +import 'dart:js_interop'; -final _inertFragment = DocumentFragment(); +import 'package:web/web.dart'; /// Sanitizes the given unsafe, untrusted HTML fragment, and returns HTML text /// that is safe to add to the DOM in a browser environment. -/// -/// This function uses the builtin Dart innerHTML sanitization provided by -/// NodeTreeSanitizer on an inert element. String? sanitizeHtmlInternal(String value) { - final inertFragment = _inertFragment..innerHtml = value; - final safeHtml = inertFragment.innerHtml; - inertFragment.children.clear(); - return safeHtml; + final template = HTMLTemplateElement(); + template.innerHTML = value.toJS; + return (template.innerHTML as JSString).toDart; } diff --git a/ngdart/lib/src/security/safe_inner_html.dart b/ngdart/lib/src/security/safe_inner_html.dart index 88b884f7ce..45a31010ee 100644 --- a/ngdart/lib/src/security/safe_inner_html.dart +++ b/ngdart/lib/src/security/safe_inner_html.dart @@ -1,6 +1,7 @@ -import 'dart:html' show Element, NodeTreeSanitizer; +import 'dart:js_interop'; import 'package:ngdart/angular.dart'; +import 'package:web/web.dart'; import 'dom_sanitization_service.dart' show SafeHtml; @@ -41,14 +42,12 @@ class SafeInnerHtmlDirective { SafeInnerHtmlDirective(this._element); @Input() - set safeInnerHtml(safeInnerHtml) { + set safeInnerHtml(dynamic safeInnerHtml) { if (safeInnerHtml is SafeHtml) { - _element.setInnerHtml( - safeInnerHtml.changingThisWillBypassSecurityTrust, - treeSanitizer: NodeTreeSanitizer.trusted, - ); + _element.innerHTML = + safeInnerHtml.changingThisWillBypassSecurityTrust.toJS; } else if (safeInnerHtml == null) { - _element.setInnerHtml(''); + _element.innerHTML = ''.toJS; } else { // A regular string is not allowed since a security audit needs to be able // to search for SafeHtml and identify all locations where we are diff --git a/ngdart/lib/src/testability/js_api.dart b/ngdart/lib/src/testability/js_api.dart index 31cd9c5981..8c9d7b70b7 100644 --- a/ngdart/lib/src/testability/js_api.dart +++ b/ngdart/lib/src/testability/js_api.dart @@ -1,19 +1,14 @@ -@JS() -library; +import 'dart:js_interop'; -import 'dart:html'; - -import 'package:js/js.dart'; +import 'package:web/web.dart'; /// A JavaScript interface for interacting with AngularDart's `Testability` API. /// /// This interfaces with a running AngularDart application. -@JS() -@anonymous -abstract class JsTestability { - external factory JsTestability({ - required bool Function() isStable, - required void Function(void Function()) whenStable, +extension type JSTestability._(JSObject _) implements JSObject { + external factory JSTestability({ + JSFunction isStable, + required JSFunction whenStable, }); /// Returns whether the application is considered stable. @@ -22,7 +17,7 @@ abstract class JsTestability { /// framework. By default, this is determined by no known asynchronous tasks /// (microtasks, or timers) being present but not yet executed within the /// framework context. - bool isStable(); + external bool isStable(); /// Invokes the provided [callback] when the application [isStable]. /// @@ -30,23 +25,21 @@ abstract class JsTestability { /// invoked, [callback] is invoked with a value of `false` for `didWork`, /// indicating that no asynchronous work was awaited before execution. /// Otherwise a value of `true` is passed. - void whenStable(void Function() callback); + external void whenStable(JSFunction callback); } /// A JavaScript interface for interacting with AngularDart's `TestabilityRegistry` API. /// /// A global registry of `Testability` instances given an app root element. -@JS() -@anonymous -abstract class JsTestabilityRegistry { - external factory JsTestabilityRegistry({ - required JsTestability? Function(Element) getAngularTestability, - required List Function() getAllAngularTestabilities, +extension type JSTestabilityRegistry._(JSObject _) implements JSObject { + external factory JSTestabilityRegistry({ + required JSFunction getAngularTestability, + required JSFunction getAllAngularTestabilities, }); /// Returns the registered testability instance for [appRoot], or `null`. - JsTestability? getAngularTestability(Element appRoot); + external JSTestability? getAngularTestability(Element appRoot); /// Returns all testability instances registered. - List getAllAngularTestabilities(); + external JSArray getAllAngularTestabilities(); } diff --git a/ngdart/lib/src/testability/js_impl.dart b/ngdart/lib/src/testability/js_impl.dart index 3d9916effe..4692ba391f 100644 --- a/ngdart/lib/src/testability/js_impl.dart +++ b/ngdart/lib/src/testability/js_impl.dart @@ -1,38 +1,41 @@ part of 'testability.dart'; @JS('ngTestabilityRegistries') -external List? _ngJsTestabilityRegistries; +external JSArray? _ngJSTestabilityRegistries; @JS('getAngularTestability') -external set _jsGetAngularTestability( - Object? Function(Element element) function); +external set _jsGetAngularTestability(JSFunction function); @JS('getAllAngularTestabilities') -external set _jsGetAllAngularTestabilities(List Function() function); +external set _jsGetAllAngularTestabilities(JSFunction function); @JS('frameworkStabilizers') -external List? _jsFrameworkStabilizers; +external JSArray? _jsFrameworkStabilizers; + +extension on JSArray { + external void push(JSFunction item); +} class _JSTestabilityProxy implements _TestabilityProxy { const _JSTestabilityProxy(); @override void addToWindow(TestabilityRegistry registry) { - var registries = _ngJsTestabilityRegistries; + var registries = _ngJSTestabilityRegistries; if (registries == null) { - registries = []; - _ngJsTestabilityRegistries = registries; - _jsGetAngularTestability = allowInterop(_getAngularTestability); - _jsGetAllAngularTestabilities = allowInterop(_getAllAngularTestabilities); - (_jsFrameworkStabilizers ??= []) - .add(allowInterop(_whenAllStable)); + registries = JSArray(); + _ngJSTestabilityRegistries = registries; + _jsGetAngularTestability = _getAngularTestability.toJS; + _jsGetAllAngularTestabilities = _getAllAngularTestabilities.toJS; + (_jsFrameworkStabilizers ??= JSArray()) + .push(_whenAllStable.toJS); } - registries.add(registry.asJsApi()); + registries.add(registry.toJS); } /// For every registered [TestabilityRegistry], tries `getAngularTestability`. - static JsTestability? _getAngularTestability(Element element) { - final registry = _ngJsTestabilityRegistries; + static JSTestability? _getAngularTestability(Element element) { + final registry = _ngJSTestabilityRegistries; if (registry == null) { return null; } @@ -46,21 +49,21 @@ class _JSTestabilityProxy implements _TestabilityProxy { } /// For every registered [TestabilityRegistry], returns the JS API for it. - static List _getAllAngularTestabilities() { - final registry = _ngJsTestabilityRegistries; - if (registry == null) { - return []; - } - final result = []; - for (var i = 0; i < registry.length; i++) { - final testabilities = registry[i].getAllAngularTestabilities(); - result.addAll(testabilities); + static JSArray _getAllAngularTestabilities() { + final registry = _ngJSTestabilityRegistries; + var result = JSArray(); + if (registry != null) { + for (var i = 0; i < registry.length; i++) { + final testabilities = registry[i].getAllAngularTestabilities(); + result = result.callMethod('concat'.toJS, testabilities) + as JSArray; + } } return result; } /// For every testability, calls [callback] when they _all_ report stable. - static void _whenAllStable(void Function() callback) { + static void _whenAllStable(JSFunction callback) { final testabilities = _getAllAngularTestabilities(); var pendingStable = testabilities.length; @@ -68,41 +71,46 @@ class _JSTestabilityProxy implements _TestabilityProxy { void decrement() { pendingStable--; if (pendingStable == 0) { - callback(); + callback.callAsFunction(); } } for (var i = 0; i < testabilities.length; i++) { - testabilities[i].whenStable(allowInterop(decrement)); + testabilities[i].whenStable(decrement.toJS); } } } extension on Testability { - JsTestability asJsApi() { - return JsTestability( - isStable: allowInterop(() => isStable), - whenStable: allowInterop(whenStable), + JSTestability get toJS { + return JSTestability( + isStable: (() => isStable).toJS, + whenStable: (JSFunction callback) { + whenStable(() { + callback.callAsFunction(); + }); + }.toJS, ); } } extension on TestabilityRegistry { - JsTestabilityRegistry asJsApi() { - JsTestability? getAngularTestability(Element element) { + JSTestabilityRegistry get toJS { + JSTestability? getAngularTestability(Element element) { final dartTestability = testabilityFor(element); - return dartTestability?.asJsApi(); + return dartTestability?.toJS; } - List getAllAngularTestabilities() { + JSArray getAllAngularTestabilities() { return allTestabilities - .map((testability) => testability.asJsApi()) - .toList(); + .map((testability) => testability.toJS) + .toList() + .toJS; } - return JsTestabilityRegistry( - getAngularTestability: allowInterop(getAngularTestability), - getAllAngularTestabilities: allowInterop(getAllAngularTestabilities), + return JSTestabilityRegistry( + getAngularTestability: getAngularTestability.toJS, + getAllAngularTestabilities: getAllAngularTestabilities.toJS, ); } } diff --git a/ngdart/lib/src/testability/testability.dart b/ngdart/lib/src/testability/testability.dart index b5d7c9b353..e79356ccf0 100644 --- a/ngdart/lib/src/testability/testability.dart +++ b/ngdart/lib/src/testability/testability.dart @@ -1,12 +1,9 @@ -@JS() -library; - import 'dart:async'; -import 'dart:html' show Element; -import 'dart:html'; +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; -import 'package:js/js.dart'; import 'package:meta/meta.dart'; +import 'package:web/web.dart'; import '../../di.dart'; import '../core/zone/ng_zone.dart'; diff --git a/ngdart/lib/src/utilities/unsafe_cast.dart b/ngdart/lib/src/utilities/unsafe_cast.dart index b0fbef4642..ecef9421a3 100644 --- a/ngdart/lib/src/utilities/unsafe_cast.dart +++ b/ngdart/lib/src/utilities/unsafe_cast.dart @@ -16,4 +16,5 @@ /// ``` @pragma('dart2js:tryInline') // Required for next pragma to be effective. @pragma('dart2js:as:trust') // Omits `as T`. +@pragma('wasm:prefer-inline') T unsafeCast(dynamic any) => any as T; diff --git a/ngdart/pubspec.yaml b/ngdart/pubspec.yaml index 64b697e893..b1f250641b 100644 --- a/ngdart/pubspec.yaml +++ b/ngdart/pubspec.yaml @@ -16,10 +16,10 @@ dependencies: built_value: ^8.9.2 collection: ^1.19.1 intl: ^0.19.0 - js: ^0.7.1 meta: ^1.16.0 ngcompiler: ^3.0.0-dev.3 stream_transform: ^2.1.0 + web: ^1.1.1 dev_dependencies: lints: ^5.0.0 diff --git a/ngtest/lib/src/bootstrap.dart b/ngtest/lib/src/bootstrap.dart index a0adbdd815..a848fe9ced 100644 --- a/ngtest/lib/src/bootstrap.dart +++ b/ngtest/lib/src/bootstrap.dart @@ -1,9 +1,9 @@ import 'dart:async'; -import 'dart:html'; import 'package:ngdart/angular.dart'; import 'package:ngdart/src/bootstrap/run.dart'; import 'package:ngdart/src/core/application_ref.dart'; +import 'package:web/web.dart'; /// Returns a future that completes with a new instantiated component. /// diff --git a/ngtest/lib/src/frontend/bed.dart b/ngtest/lib/src/frontend/bed.dart index c528f5b6d8..c6c10e7d90 100644 --- a/ngtest/lib/src/frontend/bed.dart +++ b/ngtest/lib/src/frontend/bed.dart @@ -1,7 +1,7 @@ import 'dart:async'; -import 'dart:html'; import 'package:ngdart/angular.dart'; +import 'package:web/web.dart'; import '../bootstrap.dart'; import '../errors.dart'; @@ -69,7 +69,7 @@ Future disposeAnyRunningTest() async { /// ``` class NgTestBed { static Element _defaultHost() { - final host = Element.tag('ng-test-bed'); + final host = document.createElement('ng-test-bed'); document.body!.append(host); return host; } diff --git a/ngtest/lib/src/frontend/fixture.dart b/ngtest/lib/src/frontend/fixture.dart index 47e4989465..91e44e977d 100644 --- a/ngtest/lib/src/frontend/fixture.dart +++ b/ngtest/lib/src/frontend/fixture.dart @@ -1,7 +1,6 @@ -import 'dart:html'; - import 'package:ngdart/angular.dart'; import 'package:ngdart/src/utilities.dart'; +import 'package:web/web.dart'; import 'bed.dart'; import 'stabilizer.dart'; @@ -36,7 +35,7 @@ class NgTestFixture { Future dispose() async { await update(); // Remove the test bed's host element. - _rootComponentRef.location.parent!.remove(); + _rootComponentRef.location.parentElement!.remove(); _applicationRef.dispose(); if (isDevMode) { debugClearComponentStyles(); @@ -78,7 +77,7 @@ class NgTestFixture { /// All text nodes within the fixture. /// /// Provided as a convenience to do simple `expect` matchers. - String? get text => rootElement.text; + String? get text => rootElement.textContent; /// A component instance to use for read-only operations (expect, assert) /// ONLY. diff --git a/ngtest/pubspec.yaml b/ngtest/pubspec.yaml index ed7170430f..bb8c24888e 100644 --- a/ngtest/pubspec.yaml +++ b/ngtest/pubspec.yaml @@ -14,6 +14,7 @@ dependencies: collection: ^1.19.1 meta: ^1.16.0 ngdart: ^8.0.0-dev.4 + web: ^1.1.1 dev_dependencies: build_runner: ^2.4.12 diff --git a/ngtest/test/bootstrap_test.dart b/ngtest/test/bootstrap_test.dart index 71e0cf6cee..836d846b0f 100644 --- a/ngtest/test/bootstrap_test.dart +++ b/ngtest/test/bootstrap_test.dart @@ -1,8 +1,7 @@ -import 'dart:html'; - import 'package:ngdart/angular.dart'; import 'package:ngtest/src/bootstrap.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'bootstrap_test.template.dart' as ng_generated; @@ -10,42 +9,42 @@ Injector noopInjector(Injector i) => i; void main() { test('should create a new component in the DOM', () async { - final host = Element.div(); + final host = HTMLDivElement(); final test = await bootstrapForTest( ng_generated.createNewComponentInDomFactory(), host, noopInjector, ); - expect(host.text, contains('Hello World')); + expect(host.textContent, contains('Hello World')); test.destroy(); }); test('should call a synchronous handler before initial load', () async { - final host = Element.div(); + final host = HTMLDivElement(); final test = await bootstrapForTest( ng_generated.createBeforeChangeDetectionFactory(), host, noopInjector, beforeChangeDetection: (comp) => comp.users.add('Mati'), ); - expect(host.text, contains('Hello Mati!')); + expect(host.textContent, contains('Hello Mati!')); test.destroy(); }); test('should call an asynchronous handler before initial load', () async { - final host = Element.div(); + final host = HTMLDivElement(); final test = await bootstrapForTest( ng_generated.createBeforeChangeDetectionFactory(), host, noopInjector, beforeChangeDetection: (comp) async => comp.users.add('Mati'), ); - expect(host.text, contains('Hello Mati!')); + expect(host.textContent, contains('Hello Mati!')); test.destroy(); }); test('should include user-specified providers', () async { - final host = Element.div(); + final host = HTMLDivElement(); final test = await bootstrapForTest( ng_generated.createAddProvidersFactory(), host, @@ -57,7 +56,7 @@ void main() { }); test('should be able to call injector before component creation', () async { - final host = Element.div(); + final host = HTMLDivElement(); TestService? testService; final test = await bootstrapForTest( ng_generated.createAddProvidersFactory(), @@ -80,7 +79,7 @@ void main() { test('should be able to call asynchronous injector before component creation', () async { - final host = Element.div(); + final host = HTMLDivElement(); TestService? testService; final test = await bootstrapForTest( ng_generated.createAddProvidersFactory(), diff --git a/ngtest/test/frontend/bed_error_test.dart b/ngtest/test/frontend/bed_error_test.dart index f2ff01a652..54c129bf25 100644 --- a/ngtest/test/frontend/bed_error_test.dart +++ b/ngtest/test/frontend/bed_error_test.dart @@ -1,8 +1,10 @@ import 'dart:async'; +import 'dart:js_interop'; import 'package:ngdart/angular.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'bed_error_test.template.dart' as ng; @@ -123,7 +125,8 @@ class CatchNativeEventSynchronousErrors { ).create(); expect( fixture.update((_) { - fixture.rootElement.querySelector('button')!.click(); + (fixture.rootElement.querySelector('button') as HTMLButtonElement) + .click(); }), throwsA(isStateError), ); @@ -145,7 +148,8 @@ class CatchNativeEventAsynchronousErrors { ).create(); expect( fixture.update((_) { - fixture.rootElement.querySelector('button')!.click(); + (fixture.rootElement.querySelector('button') as HTMLButtonElement) + .click(); }), throwsA(isStateError), ); @@ -229,7 +233,7 @@ class NoExceptionsSwallowedTest { expect(fixture.text, 'Hello Angular'); await fixture.update((c) => c.name = 'World'); expect(fixture.text, 'Hello World'); - final html = fixture.rootElement.innerHtml; + final html = fixture.rootElement.innerHTML as JSString; expect(html, '

Hello World

'); await fixture.dispose(); diff --git a/ngtest/test/frontend/bed_lifecycle_test.dart b/ngtest/test/frontend/bed_lifecycle_test.dart index d03f35e2fe..e8375e2146 100644 --- a/ngtest/test/frontend/bed_lifecycle_test.dart +++ b/ngtest/test/frontend/bed_lifecycle_test.dart @@ -1,8 +1,9 @@ -import 'dart:html'; +import 'dart:js_interop'; import 'package:ngdart/angular.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'bed_lifecycle_test.template.dart' as ng; @@ -11,8 +12,8 @@ void main() { late Element testRoot; setUp(() { - docRoot = Element.tag('doc-root'); - testRoot = Element.tag('ng-test-bed-example-test'); + docRoot = document.createElement('doc-root'); + testRoot = document.createElement('ng-test-bed-example-test'); docRoot.append(testRoot); }); @@ -27,12 +28,12 @@ void main() { host: testRoot, ); final NgTestFixture fixture = await testBed.create(); - expect(docRoot.text, isEmpty); + expect(docRoot.textContent, isEmpty); await fixture.update((c) => c.value = 'New value'); - expect(docRoot.text, 'New value'); + expect(docRoot.textContent, equals('New value')); await fixture.dispose(); - print(docRoot.innerHtml); - expect(docRoot.text, isEmpty); + print((docRoot.innerHTML as JSString).toDart); + expect(docRoot.textContent, isEmpty); }); test('should invoke ngAfterChanges, then ngOnInit', () async { diff --git a/ngtest/test/frontend/compatibility_test.dart b/ngtest/test/frontend/compatibility_test.dart index 10a311242b..a7e46fd7bb 100644 --- a/ngtest/test/frontend/compatibility_test.dart +++ b/ngtest/test/frontend/compatibility_test.dart @@ -1,9 +1,10 @@ -import 'dart:html'; +import 'dart:js_interop'; import 'package:ngdart/angular.dart'; import 'package:ngtest/angular_test.dart'; import 'package:ngtest/compatibility.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'compatibility_test.template.dart' as ng; @@ -12,8 +13,8 @@ void main() { late Element testRoot; setUp(() { - docRoot = Element.tag('doc-root'); - testRoot = Element.tag('ng-test-bed-example-test'); + docRoot = document.createElement('doc-root'); + testRoot = document.createElement('ng-test-bed-example-test'); docRoot.append(testRoot); }); @@ -37,13 +38,13 @@ void main() { // (our component), the node is updated (after change detection), and // after destroying the test the document root has been cleared. final fixture = await testBed.create(); - expect(docRoot.text, isEmpty); + expect(docRoot.textContent, isEmpty); testService = injectFromFixture(fixture, TestService); await fixture.update((_) => testService!.value = 'New value'); - expect(docRoot.text, 'New value'); + expect(docRoot.textContent, 'New value'); await fixture.dispose(); - print(docRoot.innerHtml); - expect(docRoot.text, isEmpty); + print((docRoot.innerHTML as JSString).toDart); + expect(docRoot.textContent, isEmpty); }); group('and beforeComponentCreated without error', () { test('should handle synchronous fn', () async { @@ -53,7 +54,7 @@ void main() { }, beforeChangeDetection: (_) { expect(testService, isNotNull); }); - expect(docRoot.text, 'New value'); + expect(docRoot.textContent, 'New value'); await fixture.dispose(); }); @@ -64,7 +65,7 @@ void main() { }, beforeChangeDetection: (_) { expect(testService, isNotNull); }); - expect(docRoot.text, 'New value'); + expect(docRoot.textContent, 'New value'); await fixture.dispose(); }); @@ -76,7 +77,7 @@ void main() { }, beforeChangeDetection: (_) { expect(testService, isNotNull); }); - expect(docRoot.text, 'New value'); + expect(docRoot.textContent, 'New value'); await fixture.dispose(); }); }); diff --git a/tool/package_versions.yaml b/tool/package_versions.yaml index d6344b5329..3c925b06d4 100644 --- a/tool/package_versions.yaml +++ b/tool/package_versions.yaml @@ -17,7 +17,6 @@ csslib: ^1.0.2 dart_style: ^2.3.7 glob: ^2.1.2 intl: ^0.19.0 -js: ^0.7.1 lints: ^5.0.0 logging: ^1.3.0 meta: ^1.16.0 @@ -35,3 +34,4 @@ string_scanner: ^1.4.0 term_glyph: ^1.2.1 test: ^1.25.9 watcher: ^1.1.0 +web: ^1.1.1 From 40db1ce2134b49ed1c725dca235db0a6f46a3243 Mon Sep 17 00:00:00 2001 From: Olzhas Suleimen Date: Mon, 21 Apr 2025 18:23:32 +0500 Subject: [PATCH 02/19] Update ngcompiler. Signed-off-by: Gavin Zhao --- .../lib/v1/src/compiler/identifiers.dart | 61 +++++++++---------- .../compiler/view_compiler/compile_query.dart | 25 ++++++-- ngcompiler/lib/v2/context.dart | 3 +- 3 files changed, 50 insertions(+), 39 deletions(-) diff --git a/ngcompiler/lib/v1/src/compiler/identifiers.dart b/ngcompiler/lib/v1/src/compiler/identifiers.dart index 49160e1cd3..ac0003e65c 100644 --- a/ngcompiler/lib/v1/src/compiler/identifiers.dart +++ b/ngcompiler/lib/v1/src/compiler/identifiers.dart @@ -289,68 +289,65 @@ class Identifiers { // Runtime is initialized by output interpreter. Compiler executes in VM and // can't import `package:web/web.dart` to initialize here. static var commentNode = CompileIdentifierMetadata( - name: 'Comment', moduleUrl: 'package:web/web.dart'); + name: 'Comment', moduleUrl: 'asset:web/lib/src/dom/dom.dart'); static var textNode = CompileIdentifierMetadata( - name: 'Text', moduleUrl: 'package:web/web.dart'); + name: 'Text', moduleUrl: 'asset:web/lib/src/dom/dom.dart'); static var document = CompileIdentifierMetadata( - name: 'document', moduleUrl: 'package:web/web.dart'); + name: 'document', moduleUrl: 'asset:web/lib/src/dom/dom.dart'); static final documentFragment = CompileIdentifierMetadata( - name: 'DocumentFragment', moduleUrl: 'package:web/web.dart'); + name: 'DocumentFragment', moduleUrl: 'asset:web/lib/src/dom/dom.dart'); static final element = CompileIdentifierMetadata( - name: 'Element', moduleUrl: 'package:web/web.dart'); + name: 'Element', moduleUrl: 'asset:web/lib/src/dom/dom.dart'); static final elementToken = identifierToken(element); static final htmlElement = CompileIdentifierMetadata( - name: 'HTMLElement', moduleUrl: 'package:web/web.dart'); + name: 'HTMLElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final htmlElementToken = identifierToken(htmlElement); static final svgSvgElement = CompileIdentifierMetadata( - name: 'SVGSVGElement', moduleUrl: 'package:web/web.dart'); + name: 'SVGSVGElement', moduleUrl: 'asset:web/lib/src/dom/svg.dart'); static final svgElement = CompileIdentifierMetadata( - name: 'SVGElement', moduleUrl: 'package:web/web.dart'); + name: 'SVGElement', moduleUrl: 'asset:web/lib/src/dom/svg.dart'); static final anchorElement = CompileIdentifierMetadata( - name: 'HTMLAnchorElement', moduleUrl: 'package:web/web.dart'); + name: 'HTMLAnchorElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final divElement = CompileIdentifierMetadata( - name: 'HTMLDivElement', moduleUrl: 'package:web/web.dart'); + name: 'HTMLDivElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final areaElement = CompileIdentifierMetadata( - name: 'HTMLAreaElement', moduleUrl: 'package:web/web.dart'); + name: 'HTMLAreaElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final audioElement = CompileIdentifierMetadata( - name: 'HTMLAudioElement', moduleUrl: 'package:web/web.dart'); + name: 'HTMLAudioElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final buttonElement = CompileIdentifierMetadata( - name: 'HTMLButtonElement', moduleUrl: 'package:web/web.dart'); + name: 'HTMLButtonElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final canvasElement = CompileIdentifierMetadata( - name: 'HTMLCanvasElement', moduleUrl: 'package:web/web.dart'); + name: 'HTMLCanvasElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final formElement = CompileIdentifierMetadata( - name: 'HTMLFormElement', moduleUrl: 'package:web/web.dart'); + name: 'HTMLFormElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final iframeElement = CompileIdentifierMetadata( - name: 'HTMLIFrameElement', moduleUrl: 'package:web/web.dart'); + name: 'HTMLIFrameElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final imageElement = CompileIdentifierMetadata( - name: 'HTMLImageElement', moduleUrl: 'package:web/web.dart'); + name: 'HTMLImageElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final inputElement = CompileIdentifierMetadata( - name: 'HTMLInputElement', moduleUrl: 'package:web/web.dart'); + name: 'HTMLInputElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final textareaElement = CompileIdentifierMetadata( - name: 'HTMLTextAreaElement', - moduleUrl: 'package:web/web.dart'); + name: 'HTMLTextAreaElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final mediaElement = CompileIdentifierMetadata( - name: 'HTMLMediaElement', moduleUrl: 'package:web/web.dart'); + name: 'HTMLMediaElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final menuElement = CompileIdentifierMetadata( - name: 'HTMLMenuElement', moduleUrl: 'package:web/web.dart'); + name: 'HTMLMenuElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final optionElement = CompileIdentifierMetadata( - name: 'HTMLOptionElement', moduleUrl: 'package:web/web.dart'); + name: 'HTMLOptionElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final oListElement = CompileIdentifierMetadata( - name: 'HTMLOListElement', moduleUrl: 'package:web/web.dart'); + name: 'HTMLOListElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final selectElement = CompileIdentifierMetadata( - name: 'HTMLSelectElement', moduleUrl: 'package:web/web.dart'); + name: 'HTMLSelectElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final tableElement = CompileIdentifierMetadata( - name: 'HTMLTableElement', moduleUrl: 'package:web/web.dart'); + name: 'HTMLTableElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final tableRowElement = CompileIdentifierMetadata( - name: 'HTMLTableRowElement', - moduleUrl: 'package:web/web.dart'); + name: 'HTMLTableRowElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final tableColElement = CompileIdentifierMetadata( - name: 'HTMLTableColElement', - moduleUrl: 'package:web/web.dart'); + name: 'HTMLTableColElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final uListElement = CompileIdentifierMetadata( - name: 'HTMLUListElement', moduleUrl: 'package:web/web.dart'); + name: 'HTMLUListElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final node = CompileIdentifierMetadata( - name: 'Node', moduleUrl: 'package:web/web.dart'); + name: 'Node', moduleUrl: 'asset:web/lib/src/dom/dom.dart'); /// A class used for message internationalization. static final intl = CompileIdentifierMetadata( diff --git a/ngcompiler/lib/v1/src/compiler/view_compiler/compile_query.dart b/ngcompiler/lib/v1/src/compiler/view_compiler/compile_query.dart index 55380d1391..c04e7de6ca 100644 --- a/ngcompiler/lib/v1/src/compiler/view_compiler/compile_query.dart +++ b/ngcompiler/lib/v1/src/compiler/view_compiler/compile_query.dart @@ -6,7 +6,7 @@ import 'compile_view.dart' show CompileView; import 'ir/provider_source.dart'; import 'ir/view_storage.dart'; import 'view_compiler_utils.dart' - show getPropertyInView, replaceReadClassMemberInExpression; + show getPropertyInView, replaceReadClassMemberInExpression, unsafeCast; const _viewQueryNodeIndex = -1; @@ -452,9 +452,9 @@ class _ListCompileQuery extends CompileQuery { if (_isSingle && results.values.isEmpty) { return const []; } - result = _createUpdatesStaticOnly(results.values); + result = _createUpdatesStaticOnly(results.values, metadata.isElementType); } else { - result = _createUpdatesNested(results.values); + result = _createUpdatesNested(results.values, metadata.isElementType); } return [ ..._createAddQueryChangeDetectorRefs(results.withChangeDetectorRefs), @@ -470,8 +470,20 @@ class _ListCompileQuery extends CompileQuery { // // * If this is for @{Content|View}Child, use the first value. // * Else, return the element(s) as a List. - o.Expression _createUpdatesStaticOnly(List values) => - _isSingle ? values.first : o.literalArr(values); + o.Expression _createUpdatesStaticOnly( + List values, + bool isElement, + ) { + if (_isSingle) { + return isElement ? unsafeCast(values.first) : values.first; + } + + if (isElement) { + values = values.map(unsafeCast).toList(); + } + + return o.literalArr(values); + } // Returns the equivalent of `{list}.isNotEmpty ? {list}.first : null`. o.Expression _firstIfNotEmpty(o.Expression list) { @@ -486,11 +498,12 @@ class _ListCompileQuery extends CompileQuery { // // * If this is for @{Content|View}Child, return the first value if not empty. // * Else, just return the {expression} itself (already a List). - o.Expression _createUpdatesNested(List values) { + o.Expression _createUpdatesNested(List values, bool isElement) { // We know there are nested views, so if the length is 1, then it must be a // `mapNestedViews` expression which returns a list. Otherwise, we wrap the // results in a list literal. final value = values.length != 1 ? o.literalArr(values) : values.first; + // TODO(ykmnkmi): add unsafeCast if needed. return _isSingle ? _firstIfNotEmpty(value) : value; } } diff --git a/ngcompiler/lib/v2/context.dart b/ngcompiler/lib/v2/context.dart index 04b2d309c6..e6e62bf423 100644 --- a/ngcompiler/lib/v2/context.dart +++ b/ngcompiler/lib/v2/context.dart @@ -71,7 +71,8 @@ Future runWithContext( ); } if (!buildCompletedOrFailed.isCompleted) { - buildCompletedOrFailed.complete(); + // TODO(ykmnkmi): check or switch back to complete. + buildCompletedOrFailed.completeError(e, s); } }, zoneSpecification: ZoneSpecification( From c1063ff52215f5523ed99f3ba10a1b729b412946 Mon Sep 17 00:00:00 2001 From: Olzhas Suleimen Date: Mon, 21 Apr 2025 18:23:56 +0500 Subject: [PATCH 03/19] Fix ngdart. Signed-off-by: Gavin Zhao --- ngdart/lib/src/runtime/dom_helpers.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ngdart/lib/src/runtime/dom_helpers.dart b/ngdart/lib/src/runtime/dom_helpers.dart index 5d97b55efe..b87ba7982f 100644 --- a/ngdart/lib/src/runtime/dom_helpers.dart +++ b/ngdart/lib/src/runtime/dom_helpers.dart @@ -248,7 +248,7 @@ void appendNodes(List nodes, Node parent) { /// Removes [nodes] from the DOM. @dart2js.noInline void removeNodes(List nodes) { - for (var i = 0, node = nodes[i], l = nodes.length; i < l; node = nodes[++i]) { + for (var i = 0, node = nodes[0], l = nodes.length; i < l; node = nodes[i++]) { node.parentNode?.removeChild(node); } } From 3992079bef950694628327af9397bd273621171c Mon Sep 17 00:00:00 2001 From: Olzhas Suleimen Date: Mon, 21 Apr 2025 18:29:08 +0500 Subject: [PATCH 04/19] Migrate ngrouter. Signed-off-by: Gavin Zhao --- .../router_link_active_directive.dart | 6 ++- .../src/directives/router_link_directive.dart | 14 +++++-- ngrouter/lib/src/location/base_href.dart | 6 +-- .../location/browser_platform_location.dart | 19 ++++----- .../src/location/hash_location_strategy.dart | 9 +++-- .../lib/src/location/location_strategy.dart | 10 ++--- .../src/location/path_location_strategy.dart | 9 +++-- .../lib/src/location/platform_location.dart | 12 +++--- .../testing/mock_location_strategy.dart | 4 +- ngrouter/pubspec.yaml | 1 + .../router_link_active_directive_test.dart | 14 +++---- .../router_link_directive_test.dart | 39 +++++++++---------- .../regression/empty_active_link_test.dart | 2 +- .../hash_location_strategy_test.dart | 13 ++++--- .../regression/routing_state_crash_test.dart | 15 +++---- ngrouter/test/revert_popstate_test.dart | 3 +- 16 files changed, 96 insertions(+), 80 deletions(-) diff --git a/ngrouter/lib/src/directives/router_link_active_directive.dart b/ngrouter/lib/src/directives/router_link_active_directive.dart index e470634d00..9843508472 100644 --- a/ngrouter/lib/src/directives/router_link_active_directive.dart +++ b/ngrouter/lib/src/directives/router_link_active_directive.dart @@ -1,9 +1,9 @@ import 'dart:async'; -import 'dart:html'; import 'package:collection/collection.dart'; import 'package:ngdart/angular.dart'; import 'package:ngdart/src/utilities.dart'; +import 'package:web/web.dart'; import '../router/router.dart'; import '../router/router_state.dart'; @@ -80,6 +80,8 @@ class RouterLinkActive implements AfterViewInit, OnDestroy { break; } } - _element.classes.toggleAll(_classes, isActive); + for (var i = 0; i < _classes.length; i++) { + _element.classList.toggle(_classes[i], isActive); + } } } diff --git a/ngrouter/lib/src/directives/router_link_directive.dart b/ngrouter/lib/src/directives/router_link_directive.dart index b9b5141498..178cd533f3 100644 --- a/ngrouter/lib/src/directives/router_link_directive.dart +++ b/ngrouter/lib/src/directives/router_link_directive.dart @@ -1,8 +1,16 @@ import 'dart:async'; -import 'dart:html' - show AnchorElement, Element, Event, KeyboardEvent, KeyCode, MouseEvent; +import 'dart:js_interop'; import 'package:ngdart/angular.dart'; +import 'package:web/web.dart' + show + Element, + ElementEventGetters, + Event, + HTMLAnchorElement, + KeyCode, + KeyboardEvent, + MouseEvent; import '../location.dart' show Location; import '../router/navigation_params.dart'; @@ -42,7 +50,7 @@ class RouterLink implements OnDestroy { // The browser will synthesize a click event for anchor elements when they // receive an Enter key press. For other elements, we must manually add a // key press listener to ensure the link remains keyboard accessible. - if (element is! AnchorElement) { + if (!element.isA()) { _keyPressSubscription = element.onKeyPress.listen(_onKeyPress); } } diff --git a/ngrouter/lib/src/location/base_href.dart b/ngrouter/lib/src/location/base_href.dart index 29c420930f..6084e1ce89 100644 --- a/ngrouter/lib/src/location/base_href.dart +++ b/ngrouter/lib/src/location/base_href.dart @@ -1,6 +1,6 @@ -import 'dart:html'; +import 'package:web/web.dart'; -final _urlParsingNode = AnchorElement(); +final _urlParsingNode = HTMLAnchorElement(); Element? _baseElement; String? baseHrefFromDOM() { @@ -19,6 +19,6 @@ String? _getBaseElementHref() { // based on urlUtils.js in AngularJS 1. String _relativePath(String url) { _urlParsingNode.href = url; - var pathname = _urlParsingNode.pathname!; + var pathname = _urlParsingNode.pathname; return (pathname.isEmpty || pathname[0] == '/') ? pathname : '/$pathname'; } diff --git a/ngrouter/lib/src/location/browser_platform_location.dart b/ngrouter/lib/src/location/browser_platform_location.dart index de9bde806a..608d1f808c 100644 --- a/ngrouter/lib/src/location/browser_platform_location.dart +++ b/ngrouter/lib/src/location/browser_platform_location.dart @@ -1,6 +1,7 @@ -import 'dart:html'; +import 'dart:js_interop'; import 'package:ngdart/angular.dart' show Injectable; +import 'package:web/web.dart'; import 'base_href.dart'; import 'platform_location.dart'; @@ -21,23 +22,23 @@ class BrowserPlatformLocation extends PlatformLocation { String? getBaseHrefFromDOM() => baseHrefFromDOM(); @override - void onPopState(EventListener fn) { - window.addEventListener('popstate', fn, false); + void onPopState(void Function(Event event) fn) { + window.addEventListener('popstate', fn.toJS, false.toJS); } @override - void onHashChange(EventListener fn) { - window.addEventListener('hashchange', fn, false); + void onHashChange(void Function(Event event) fn) { + window.addEventListener('hashchange', fn.toJS, false.toJS); } @override String get pathname { - return location.pathname!; + return location.pathname; } @override String get search { - return location.search!; + return location.search; } @override @@ -50,12 +51,12 @@ class BrowserPlatformLocation extends PlatformLocation { } @override - void pushState(Object? state, String title, String? url) { + void pushState(JSAny? state, String title, String? url) { _history.pushState(state, title, url); } @override - void replaceState(Object? state, String title, String? url) { + void replaceState(JSAny? state, String title, String? url) { _history.replaceState(state, title, url); } diff --git a/ngrouter/lib/src/location/hash_location_strategy.dart b/ngrouter/lib/src/location/hash_location_strategy.dart index f974b8f560..276c8cf9e3 100644 --- a/ngrouter/lib/src/location/hash_location_strategy.dart +++ b/ngrouter/lib/src/location/hash_location_strategy.dart @@ -1,6 +1,7 @@ -import 'dart:html' as html; +import 'dart:js_interop'; import 'package:ngdart/angular.dart' show Injectable, Inject, Optional; +import 'package:web/web.dart' hide Location; import 'location.dart' show Location; import 'location_strategy.dart' show LocationStrategy, appBaseHref; @@ -43,7 +44,7 @@ class HashLocationStrategy extends LocationStrategy { ]) : _baseHref = baseHref ?? ''; @override - void onPopState(html.EventListener fn) { + void onPopState(void Function(Event event) fn) { _platformLocation.onPopState(fn); } @@ -85,7 +86,7 @@ class HashLocationStrategy extends LocationStrategy { } @override - void pushState(Object? state, String title, String url, String queryParams) { + void pushState(JSAny? state, String title, String url, String queryParams) { var externalUrl = prepareExternalUrl(url + Location.normalizeQueryParams(queryParams)); _platformLocation.pushState(state, title, externalUrl); @@ -93,7 +94,7 @@ class HashLocationStrategy extends LocationStrategy { @override void replaceState( - Object? state, String title, String url, String queryParams) { + JSAny? state, String title, String url, String queryParams) { var normalizedUrl = prepareExternalUrl(url + Location.normalizeQueryParams(queryParams)); _platformLocation.replaceState(state, title, normalizedUrl); diff --git a/ngrouter/lib/src/location/location_strategy.dart b/ngrouter/lib/src/location/location_strategy.dart index d5bc17199c..86e02199fc 100644 --- a/ngrouter/lib/src/location/location_strategy.dart +++ b/ngrouter/lib/src/location/location_strategy.dart @@ -1,6 +1,7 @@ -import 'dart:html'; +import 'dart:js_interop'; import 'package:ngdart/angular.dart' show OpaqueToken; +import 'package:web/web.dart'; /// `LocationStrategy` is responsible for representing and reading route state /// from the browser's URL. Angular provides two strategies: @@ -20,12 +21,11 @@ abstract class LocationStrategy { String path(); String hash(); String prepareExternalUrl(String internal); - void pushState(Object? state, String title, String url, String queryParams); - void replaceState( - Object? state, String title, String url, String queryParams); + void pushState(JSAny? state, String title, String url, String queryParams); + void replaceState(JSAny? state, String title, String url, String queryParams); void forward(); void back(); - void onPopState(EventListener fn); + void onPopState(void Function(Event event) fn); String getBaseHref(); } diff --git a/ngrouter/lib/src/location/path_location_strategy.dart b/ngrouter/lib/src/location/path_location_strategy.dart index 65e56e1022..7667ad6dbe 100644 --- a/ngrouter/lib/src/location/path_location_strategy.dart +++ b/ngrouter/lib/src/location/path_location_strategy.dart @@ -1,6 +1,7 @@ -import 'dart:html' as html; +import 'dart:js_interop'; import 'package:ngdart/angular.dart' show Injectable, Inject, Optional; +import 'package:web/web.dart' hide Location; import 'location.dart' show Location; import 'location_strategy.dart' show LocationStrategy, appBaseHref; @@ -39,7 +40,7 @@ class PathLocationStrategy extends LocationStrategy { } @override - void onPopState(html.EventListener fn) { + void onPopState(void Function(Event event) fn) { _platformLocation.onPopState(fn); } @@ -60,7 +61,7 @@ class PathLocationStrategy extends LocationStrategy { Location.normalizeQueryParams(_platformLocation.search); @override - void pushState(Object? state, String title, String url, String queryParams) { + void pushState(JSAny? state, String title, String url, String queryParams) { var externalUrl = prepareExternalUrl(url + Location.normalizeQueryParams(queryParams)); _platformLocation.pushState(state, title, externalUrl); @@ -68,7 +69,7 @@ class PathLocationStrategy extends LocationStrategy { @override void replaceState( - Object? state, String title, String url, String queryParams) { + JSAny? state, String title, String url, String queryParams) { var externalUrl = prepareExternalUrl(url + Location.normalizeQueryParams(queryParams)); _platformLocation.replaceState(state, title, externalUrl); diff --git a/ngrouter/lib/src/location/platform_location.dart b/ngrouter/lib/src/location/platform_location.dart index e1a51fb390..a1332a6d46 100644 --- a/ngrouter/lib/src/location/platform_location.dart +++ b/ngrouter/lib/src/location/platform_location.dart @@ -1,4 +1,6 @@ -import 'dart:html'; +import 'dart:js_interop'; + +import 'package:web/web.dart'; /// This class should not be used directly by an application developer. Instead, use /// [Location]. @@ -24,13 +26,13 @@ import 'dart:html'; /// they are all platform independent. abstract class PlatformLocation { String? getBaseHrefFromDOM(); - void onPopState(EventListener fn); - void onHashChange(EventListener fn); + void onPopState(void Function(Event event) fn); + void onHashChange(void Function(Event event) fn); String get pathname; String get search; String get hash; - void replaceState(Object? state, String title, String? url); - void pushState(Object? state, String title, String? url); + void replaceState(JSAny? state, String title, String? url); + void pushState(JSAny? state, String title, String? url); void forward(); void back(); } diff --git a/ngrouter/lib/src/location/testing/mock_location_strategy.dart b/ngrouter/lib/src/location/testing/mock_location_strategy.dart index e110ae9aea..764c21ebc7 100644 --- a/ngrouter/lib/src/location/testing/mock_location_strategy.dart +++ b/ngrouter/lib/src/location/testing/mock_location_strategy.dart @@ -1,9 +1,9 @@ import 'dart:async'; -import 'dart:html' show EventListener, PopStateEvent; import 'package:ngdart/angular.dart' show Injectable; import 'package:ngrouter/src/location/location_strategy.dart' show LocationStrategy; +import 'package:web/web.dart' hide Location; /// A mock implementation of [LocationStrategy] that allows tests to fire /// simulated location events. @@ -56,7 +56,7 @@ class MockLocationStrategy extends LocationStrategy { } @override - void onPopState(EventListener fn) { + void onPopState(void Function(Event event) fn) { _subject.stream.listen(fn); } diff --git a/ngrouter/pubspec.yaml b/ngrouter/pubspec.yaml index 74fef0244e..9582a4b073 100644 --- a/ngrouter/pubspec.yaml +++ b/ngrouter/pubspec.yaml @@ -13,6 +13,7 @@ resolution: workspace dependencies: collection: ^1.19.1 ngdart: ^8.0.0-dev.4 + web: ^1.1.1 dev_dependencies: async: ^2.12.0 diff --git a/ngrouter/test/directives/router_link_active_directive_test.dart b/ngrouter/test/directives/router_link_active_directive_test.dart index 792f1008fe..6b91ebd9a2 100644 --- a/ngrouter/test/directives/router_link_active_directive_test.dart +++ b/ngrouter/test/directives/router_link_active_directive_test.dart @@ -35,11 +35,11 @@ void main() { fakeRouter.current = RouterState('/user/jill', const []); }); final anchor = fixture.rootElement.querySelector('a')!; - expect(anchor.classes, isEmpty); + expect(anchor.classList, hasLength(0)); await fixture.update((_) { fakeRouter.current = RouterState('/user/bob', const []); }); - expect(anchor.classes, contains('active-link')); + expect(anchor.classList.contains('active-link'), isTrue); }); test('should validate queryParams and fragment', () async { @@ -50,22 +50,22 @@ void main() { fakeRouter.current = RouterState('/user/bob', const []); }); final anchor = fixture.rootElement.querySelector('a')!; - expect(anchor.classes, isEmpty); + expect(anchor.classList, hasLength(0)); await fixture.update((_) { fakeRouter.current = RouterState('/user/bob', const [], queryParameters: {'param': '1'}); }); - expect(anchor.classes, isEmpty); + expect(anchor.classList, hasLength(0)); await fixture.update((_) { fakeRouter.current = RouterState('/user/bob', const [], fragment: 'frag'); }); - expect(anchor.classes, isEmpty); + expect(anchor.classList, hasLength(0)); await fixture.update((_) { fakeRouter.current = RouterState('/user/bob', const [], queryParameters: {'param': '1'}, fragment: 'frag'); }); - expect(anchor.classes, contains('active-link')); + expect(anchor.classList.contains('active-link'), isTrue); }); test( @@ -80,7 +80,7 @@ void main() { queryParameters: {'param': '1'}, fragment: 'frag'); }); final anchor = fixture.rootElement.querySelector('a')!; - expect(anchor.classes, contains('active-link')); + expect(anchor.classList.contains('active-link'), isTrue); }); } diff --git a/ngrouter/test/directives/router_link_directive_test.dart b/ngrouter/test/directives/router_link_directive_test.dart index 37669b6a9a..6dd81e4455 100644 --- a/ngrouter/test/directives/router_link_directive_test.dart +++ b/ngrouter/test/directives/router_link_directive_test.dart @@ -1,11 +1,12 @@ -import 'dart:html' hide Location; -import 'dart:js'; +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; import 'package:ngdart/angular.dart'; import 'package:ngrouter/ngrouter.dart'; import 'package:ngrouter/testing.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart' hide Location; import 'router_link_directive_test.template.dart' as ng; @@ -33,7 +34,7 @@ void main() { ).addInjector(addInjector).create(beforeChangeDetection: (comp) { comp.routerLink = '/users/bob'; }); - final anchor = fixture.rootElement.querySelector('a') as AnchorElement; + final anchor = fixture.rootElement.querySelector('a') as HTMLAnchorElement; expect(anchor.pathname, '/users/bob'); expect(fakeRouter.lastNavigatedPath, isNull); await fixture.update((_) => anchor.click()); @@ -58,7 +59,7 @@ void main() { ).addInjector(addInjector).create(beforeChangeDetection: (comp) { comp.routerLink = '/users/bob?param1=one¶m2=2#frag'; }); - final anchor = fixture.rootElement.querySelector('a') as AnchorElement; + final anchor = fixture.rootElement.querySelector('a') as HTMLAnchorElement; expect(anchor.pathname, '/users/bob'); await fixture.update((_) => anchor.click()); expect(fakeRouter.lastNavigatedPath, '/users/bob'); @@ -75,7 +76,7 @@ void main() { ).addInjector(addInjector).create(beforeChangeDetection: (comp) { comp.routerLink = '/users/bob'; }); - final anchor = fixture.rootElement.querySelector('a') as AnchorElement; + final anchor = fixture.rootElement.querySelector('a') as HTMLAnchorElement; expect(anchor.pathname, '/users/bob'); expect(anchor.target, '_parent'); await fixture.update((_) => anchor.click()); @@ -85,9 +86,7 @@ void main() { @Component( selector: 'test-router-link', - directives: [ - RouterLink, - ], + directives: [RouterLink], template: r''' ''', @@ -107,9 +106,7 @@ class TestRouterLinkKeyPress { @Component( selector: 'test-router-link', - directives: [ - RouterLink, - ], + directives: [RouterLink], template: r''' ''', @@ -174,21 +171,21 @@ Event createKeyboardEvent( bool shiftKey = false, bool metaKey = false, }) { - if (!context.hasProperty(_createKeyboardEventName)) { + if (!globalContext.has(_createKeyboardEventName)) { final script = document.createElement('script') ..setAttribute('type', 'text/javascript') - ..text = _createKeyboardEventScript; + ..textContent = _createKeyboardEventScript; document.body!.append(script); } - return context.callMethod( - _createKeyboardEventName, + return globalContext.callMethodVarArgs( + _createKeyboardEventName.toJS, [ - type, - keyCode, - ctrlKey, - altKey, - shiftKey, - metaKey, + type.toJS, + keyCode.toJS, + ctrlKey.toJS, + altKey.toJS, + shiftKey.toJS, + metaKey.toJS, ], ) as Event; } diff --git a/ngrouter/test/regression/empty_active_link_test.dart b/ngrouter/test/regression/empty_active_link_test.dart index 89389eed7d..ede86c1983 100644 --- a/ngrouter/test/regression/empty_active_link_test.dart +++ b/ngrouter/test/regression/empty_active_link_test.dart @@ -15,7 +15,7 @@ void main() { .addInjector(injector); final testFixture = await testBed.create(); final anchor = testFixture.rootElement.querySelector('a')!; - expect(anchor.classes, contains(AppComponent.activeClassName)); + expect(anchor.classList.contains(AppComponent.activeClassName), isTrue); }); } diff --git a/ngrouter/test/regression/hash_location_strategy_test.dart b/ngrouter/test/regression/hash_location_strategy_test.dart index 252aed0005..729294d2ee 100644 --- a/ngrouter/test/regression/hash_location_strategy_test.dart +++ b/ngrouter/test/regression/hash_location_strategy_test.dart @@ -1,19 +1,22 @@ -import 'dart:html'; - import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:ngdart/angular.dart'; import 'package:ngrouter/ngrouter.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; -@GenerateNiceMocks([MockSpec()]) +// TODO(ykmnkmi): replace with BrowserPlatformLocation when `mockito` supports +// extension types. +@GenerateNiceMocks([MockSpec()]) import 'hash_location_strategy_test.mocks.dart'; // ignore: uri_does_not_exist import 'hash_location_strategy_test.template.dart' as ng; +// TODO(ykmnkmi): replace with MockBrowserPlatformLocation when `mockito` +// supports extension types. // ignore: undefined_function -final platformLocation = MockBrowserPlatformLocation(); +final platformLocation = MockPlatformLocation(); void main() { setUp(() { @@ -59,7 +62,7 @@ class AppComponent { static final routes = [fooRoute]; @ViewChild('routerLink') - HtmlElement? anchor; + HTMLAnchorElement? anchor; } @Component(selector: 'foo', template: '') diff --git a/ngrouter/test/regression/routing_state_crash_test.dart b/ngrouter/test/regression/routing_state_crash_test.dart index 7b56011938..8bb2ca567b 100644 --- a/ngrouter/test/regression/routing_state_crash_test.dart +++ b/ngrouter/test/regression/routing_state_crash_test.dart @@ -1,10 +1,11 @@ import 'dart:async'; -import 'dart:html'; +import 'dart:js_interop'; import 'package:ngdart/angular.dart'; import 'package:ngrouter/ngrouter.dart'; import 'package:ngrouter/testing.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'routing_state_crash_test.template.dart' as ng; @@ -33,24 +34,24 @@ void main() { await onStable(); expect(_logs, isEmpty); expect(locationStrategy.path(), isEmpty); - expect(routeContainer.text, contains('Home Page')); + expect(routeContainer.textContent, contains('Home Page')); // "Navigate" to /another await appComponent.instance.updateUrl('/another'); expect(_logs, isEmpty); - expect(routeContainer.text, contains('Another Page')); + expect(routeContainer.textContent, contains('Another Page')); // "Navigate" to /throws. await appComponent.instance.updateUrl('/throws'); expect(_logs, [contains('$IntentionalException')]); // Since navigation fails, we should still be at the previous route. - expect(routeContainer.text, contains('Another Page')); + expect(routeContainer.textContent, contains('Another Page')); // "Navigate" back to /home. _logs.clear(); await appComponent.instance.updateUrl('/home'); expect(_logs, isEmpty); - expect(routeContainer.text, contains('Home Page')); + expect(routeContainer.textContent, contains('Home Page')); }); } @@ -74,9 +75,9 @@ class LoggingExceptionHandler implements ExceptionHandler { _logs.add('$exception: $stack'); if (exception is! IntentionalException) { - window.console.error('$exception\n$stack'); + console.error('$exception\n$stack'.toJS); } else { - window.console.info('ExceptionHandler caught the intentional exception'); + console.info('ExceptionHandler caught the intentional exception'.toJS); } } } diff --git a/ngrouter/test/revert_popstate_test.dart b/ngrouter/test/revert_popstate_test.dart index 34226678e9..64ffe1a49f 100644 --- a/ngrouter/test/revert_popstate_test.dart +++ b/ngrouter/test/revert_popstate_test.dart @@ -1,9 +1,8 @@ -import 'dart:html' show window; - import 'package:ngdart/angular.dart'; import 'package:ngrouter/ngrouter.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart' show WindowEventGetters, window; import 'revert_popstate_test.template.dart' as ng; From 81e9ecf083cf7568c106a95210bbecdb8e165045 Mon Sep 17 00:00:00 2001 From: Olzhas Suleimen Date: Mon, 21 Apr 2025 19:05:49 +0500 Subject: [PATCH 05/19] Migrate ngforms. Signed-off-by: Gavin Zhao --- ngforms/lib/src/directives/abstract_form.dart | 2 +- .../directives/checkbox_value_accessor.dart | 9 +- .../directives/default_value_accessor.dart | 11 +- .../src/directives/number_value_accessor.dart | 8 +- .../radio_control_value_accessor.dart | 9 +- .../select_control_value_accessor.dart | 15 +-- ngforms/lib/src/directives/shared.dart | 10 +- ngforms/pubspec.yaml | 1 + ngforms/test/accessor_test.dart | 9 +- ngforms/test/directives_test.dart | 15 +-- ngforms/test/directives_test.mocks.dart | 66 +++++++++ ngforms/test/integration_test.dart | 127 +++++++++++------- ngforms/test/ng_control_group_test.dart | 5 +- ngforms/test/ng_control_name_test.dart | 5 +- ngforms/test/ng_control_repeated_test.dart | 2 +- ngforms/test/ng_form_control_test.dart | 5 +- ngforms/test/ng_form_test.dart | 9 +- 17 files changed, 198 insertions(+), 110 deletions(-) create mode 100644 ngforms/test/directives_test.mocks.dart diff --git a/ngforms/lib/src/directives/abstract_form.dart b/ngforms/lib/src/directives/abstract_form.dart index 933b4cbfd4..e2d0a698c7 100644 --- a/ngforms/lib/src/directives/abstract_form.dart +++ b/ngforms/lib/src/directives/abstract_form.dart @@ -1,7 +1,7 @@ import 'dart:async'; -import 'dart:html' show Event; import 'package:ngdart/angular.dart'; +import 'package:web/web.dart' show Event; import '../model.dart'; import 'control_container.dart'; diff --git a/ngforms/lib/src/directives/checkbox_value_accessor.dart b/ngforms/lib/src/directives/checkbox_value_accessor.dart index 9644196b9d..87193dafb8 100644 --- a/ngforms/lib/src/directives/checkbox_value_accessor.dart +++ b/ngforms/lib/src/directives/checkbox_value_accessor.dart @@ -1,6 +1,5 @@ -import 'dart:html'; - import 'package:ngdart/angular.dart'; +import 'package:web/web.dart'; import 'control_value_accessor.dart' show ChangeHandler, ControlValueAccessor, ngValueAccessor, TouchHandler; @@ -26,10 +25,10 @@ const checkboxValueAccessor = ExistingProvider.forToken( class CheckboxControlValueAccessor extends Object with TouchHandler, ChangeHandler implements ControlValueAccessor { - final InputElement _element; + final HTMLInputElement _element; - CheckboxControlValueAccessor(HtmlElement element) - : _element = element as InputElement; + CheckboxControlValueAccessor(HTMLElement element) + : _element = element as HTMLInputElement; @HostListener('change', ['\$event.target.checked']) void handleChange(bool checked) { diff --git a/ngforms/lib/src/directives/default_value_accessor.dart b/ngforms/lib/src/directives/default_value_accessor.dart index 0c28cdbee4..5fd757a637 100644 --- a/ngforms/lib/src/directives/default_value_accessor.dart +++ b/ngforms/lib/src/directives/default_value_accessor.dart @@ -1,8 +1,9 @@ -import 'dart:html'; -import 'dart:js_util' as js_util; +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; import 'package:ngdart/angular.dart'; import 'package:ngforms/src/directives/shared.dart' show setElementDisabled; +import 'package:web/web.dart'; import 'control_value_accessor.dart'; import 'ng_control_name.dart'; @@ -31,7 +32,7 @@ const defaultValueAccessor = ExistingProvider.forToken( class DefaultValueAccessor extends Object with TouchHandler, ChangeHandler implements ControlValueAccessor { - final HtmlElement _element; + final HTMLElement _element; DefaultValueAccessor(this._element); @@ -42,8 +43,8 @@ class DefaultValueAccessor extends Object @override void writeValue(value) { - var normalizedValue = value ?? ''; - js_util.setProperty(_element, 'value', normalizedValue); + var normalizedValue = value as String? ?? ''; + _element['value'] = normalizedValue.toJS; } @override diff --git a/ngforms/lib/src/directives/number_value_accessor.dart b/ngforms/lib/src/directives/number_value_accessor.dart index 1f93ad71b2..169fed7186 100644 --- a/ngforms/lib/src/directives/number_value_accessor.dart +++ b/ngforms/lib/src/directives/number_value_accessor.dart @@ -1,6 +1,5 @@ -import 'dart:html'; - import 'package:ngdart/angular.dart'; +import 'package:web/web.dart'; import 'control_value_accessor.dart' show ChangeHandler, ControlValueAccessor, ngValueAccessor, TouchHandler; @@ -25,9 +24,10 @@ const numberValueAccessor = ExistingProvider.forToken( class NumberValueAccessor extends Object with TouchHandler, ChangeHandler implements ControlValueAccessor { - final InputElement _element; + final HTMLInputElement _element; - NumberValueAccessor(HtmlElement element) : _element = element as InputElement; + NumberValueAccessor(HTMLElement element) + : _element = element as HTMLInputElement; @HostListener('change', ['\$event.target.value']) @HostListener('input', ['\$event.target.value']) diff --git a/ngforms/lib/src/directives/radio_control_value_accessor.dart b/ngforms/lib/src/directives/radio_control_value_accessor.dart index 9f0d4657d0..88b64b5119 100644 --- a/ngforms/lib/src/directives/radio_control_value_accessor.dart +++ b/ngforms/lib/src/directives/radio_control_value_accessor.dart @@ -1,8 +1,9 @@ -import 'dart:html'; -import 'dart:js_util' as js_util; +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; import 'package:ngdart/angular.dart'; import 'package:ngforms/src/directives/shared.dart' show setElementDisabled; +import 'package:web/web.dart'; import 'control_value_accessor.dart' show ChangeHandler, ControlValueAccessor, ngValueAccessor, TouchHandler; @@ -76,7 +77,7 @@ class RadioButtonState { class RadioControlValueAccessor extends Object with TouchHandler, ChangeHandler implements ControlValueAccessor, OnDestroy, OnInit { - final HtmlElement _element; + final HTMLInputElement _element; final RadioControlRegistry _registry; final Injector _injector; RadioButtonState? _state; @@ -108,7 +109,7 @@ class RadioControlValueAccessor extends Object void writeValue(RadioButtonState? value) { _state = value; if (value?.checked ?? false) { - js_util.setProperty(_element, 'checked', true); + _element['checked'] = true.toJS; } } diff --git a/ngforms/lib/src/directives/select_control_value_accessor.dart b/ngforms/lib/src/directives/select_control_value_accessor.dart index eacc4e27a8..52cc63565f 100644 --- a/ngforms/lib/src/directives/select_control_value_accessor.dart +++ b/ngforms/lib/src/directives/select_control_value_accessor.dart @@ -1,7 +1,6 @@ -import 'dart:html'; - import 'package:ngdart/angular.dart'; import 'package:ngdart/src/utilities.dart'; +import 'package:web/web.dart'; import 'control_value_accessor.dart' show ChangeHandler, ControlValueAccessor, ngValueAccessor, TouchHandler; @@ -40,13 +39,13 @@ String _extractId(String valueString) => valueString.split(':')[0]; class SelectControlValueAccessor extends Object with TouchHandler, ChangeHandler implements ControlValueAccessor { - final SelectElement _element; + final HTMLSelectElement _element; Object? value; final Map _optionMap = {}; num _idCounter = 0; - SelectControlValueAccessor(HtmlElement element) - : _element = element as SelectElement; + SelectControlValueAccessor(HTMLElement element) + : _element = element as HTMLSelectElement; @HostListener('change', ['\$event.target.value']) void handleChange(String value) { @@ -91,11 +90,11 @@ class SelectControlValueAccessor extends Object selector: 'option', ) class NgSelectOption implements OnDestroy { - final OptionElement _element; + final HTMLOptionElement _element; final SelectControlValueAccessor? _select; late final String id; - NgSelectOption(HtmlElement element, @Optional() @Host() this._select) - : _element = element as OptionElement { + NgSelectOption(HTMLElement element, @Optional() @Host() this._select) + : _element = element as HTMLOptionElement { if (_select != null) id = _select._registerOption(); } diff --git a/ngforms/lib/src/directives/shared.dart b/ngforms/lib/src/directives/shared.dart index 748c510137..8155ea2dee 100644 --- a/ngforms/lib/src/directives/shared.dart +++ b/ngforms/lib/src/directives/shared.dart @@ -1,5 +1,7 @@ -import 'dart:html'; -import 'dart:js_util' as js_util; +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; + +import 'package:web/web.dart'; import '../model.dart' show Control, AbstractControlGroup; import '../validators.dart' show Validators; @@ -95,6 +97,6 @@ ControlValueAccessor? selectValueAccessor( return null; } -void setElementDisabled(HtmlElement element, bool isDisabled) { - js_util.setProperty(element, 'disabled', isDisabled); +void setElementDisabled(HTMLElement element, bool isDisabled) { + element['disabled'] = isDisabled.toJS; } diff --git a/ngforms/pubspec.yaml b/ngforms/pubspec.yaml index fd5776aa3e..728988245b 100644 --- a/ngforms/pubspec.yaml +++ b/ngforms/pubspec.yaml @@ -13,6 +13,7 @@ resolution: workspace dependencies: meta: ^1.16.0 ngdart: ^8.0.0-dev.4 + web: ^1.1.1 dev_dependencies: build_runner: ^2.4.12 diff --git a/ngforms/test/accessor_test.dart b/ngforms/test/accessor_test.dart index bdbb8fb5e9..0d2ba66e70 100644 --- a/ngforms/test/accessor_test.dart +++ b/ngforms/test/accessor_test.dart @@ -1,10 +1,11 @@ -import 'dart:html'; -import 'dart:js_util' as js_util; +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; import 'package:ngdart/angular.dart'; import 'package:ngforms/ngforms.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'accessor_test.template.dart' as ng; @@ -70,7 +71,7 @@ typedef ChangeFunctionSimple = dynamic Function(dynamic value); ], ) class IntValueAccessor implements ControlValueAccessor, Validator { - final HtmlElement _elementRef; + final HTMLElement _elementRef; @HostListener('input') void onChangeBinding() => onChange(null); @@ -91,7 +92,7 @@ class IntValueAccessor implements ControlValueAccessor, Validator { @override void writeValue(dynamic value) { var normalizedValue = value!.toString(); - js_util.setProperty(_elementRef, 'value', normalizedValue); + _elementRef['value'] = normalizedValue.toJS; } @override diff --git a/ngforms/test/directives_test.dart b/ngforms/test/directives_test.dart index 01963b9fa8..7243035a90 100644 --- a/ngforms/test/directives_test.dart +++ b/ngforms/test/directives_test.dart @@ -1,9 +1,8 @@ -import 'dart:html'; - import 'package:mockito/annotations.dart'; import 'package:ngforms/ngforms.dart'; import 'package:ngforms/src/directives/shared.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; @GenerateMocks([ControlValueAccessor]) import 'directives_test.mocks.dart'; // ignore: uri_does_not_exist @@ -41,7 +40,7 @@ void main() { late DefaultValueAccessor defaultAccessor; setUp(() { - defaultAccessor = DefaultValueAccessor(InputElement()); + defaultAccessor = DefaultValueAccessor(HTMLInputElement()); }); test('should throw when given an empty array', () { expect(() => selectValueAccessor([]), @@ -51,25 +50,25 @@ void main() { expect(selectValueAccessor([defaultAccessor]), defaultAccessor); }); test('should return checkbox accessor when provided', () { - var checkboxAccessor = CheckboxControlValueAccessor(InputElement()); + var checkboxAccessor = CheckboxControlValueAccessor(HTMLInputElement()); expect(selectValueAccessor([defaultAccessor, checkboxAccessor]), checkboxAccessor); }); test('should return select accessor when provided', () { - var selectAccessor = SelectControlValueAccessor(SelectElement()); + var selectAccessor = SelectControlValueAccessor(HTMLSelectElement()); expect(selectValueAccessor([defaultAccessor, selectAccessor]), selectAccessor); }); test('should throw when more than one build-in accessor is provided', () { - var checkboxAccessor = CheckboxControlValueAccessor(InputElement()); - var selectAccessor = SelectControlValueAccessor(SelectElement()); + var checkboxAccessor = CheckboxControlValueAccessor(HTMLInputElement()); + var selectAccessor = SelectControlValueAccessor(HTMLSelectElement()); expect(() => selectValueAccessor([checkboxAccessor, selectAccessor]), throwsWith('More than one built-in value accessor matches')); }); test('should return custom accessor when provided', () { // ignore: undefined_function var customAccessor = MockControlValueAccessor(); - var checkboxAccessor = CheckboxControlValueAccessor(InputElement()); + var checkboxAccessor = CheckboxControlValueAccessor(HTMLInputElement()); expect( selectValueAccessor( [defaultAccessor, customAccessor, checkboxAccessor]), diff --git a/ngforms/test/directives_test.mocks.dart b/ngforms/test/directives_test.mocks.dart new file mode 100644 index 0000000000..0c9a490516 --- /dev/null +++ b/ngforms/test/directives_test.mocks.dart @@ -0,0 +1,66 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in ngforms/test/directives_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:mockito/mockito.dart' as _i1; +import 'package:ngforms/src/directives/control_value_accessor.dart' as _i2; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +/// A class which mocks [ControlValueAccessor]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockControlValueAccessor extends _i1.Mock + implements _i2.ControlValueAccessor { + MockControlValueAccessor() { + _i1.throwOnMissingStub(this); + } + + @override + void writeValue(T? obj) => super.noSuchMethod( + Invocation.method( + #writeValue, + [obj], + ), + returnValueForMissingStub: null, + ); + + @override + void registerOnChange(_i2.ChangeFunction? f) => super.noSuchMethod( + Invocation.method( + #registerOnChange, + [f], + ), + returnValueForMissingStub: null, + ); + + @override + void registerOnTouched(_i2.TouchFunction? f) => super.noSuchMethod( + Invocation.method( + #registerOnTouched, + [f], + ), + returnValueForMissingStub: null, + ); + + @override + void onDisabledChanged(bool? isDisabled) => super.noSuchMethod( + Invocation.method( + #onDisabledChanged, + [isDisabled], + ), + returnValueForMissingStub: null, + ); +} diff --git a/ngforms/test/integration_test.dart b/ngforms/test/integration_test.dart index 0bc4fd8023..3d7ed6982c 100644 --- a/ngforms/test/integration_test.dart +++ b/ngforms/test/integration_test.dart @@ -1,15 +1,15 @@ import 'dart:async'; -import 'dart:html'; import 'package:ngdart/angular.dart'; import 'package:ngforms/ngforms.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'integration_test.template.dart' as ng; void dispatchEvent(Element element, String eventType) { - element.dispatchEvent(Event(eventType, canBubble: true)); + element.dispatchEvent(Event(eventType, EventInit(bubbles: true))); } void main() { @@ -22,7 +22,8 @@ void main() { beforeChangeDetection: (InputFormTest component) { component.form = ControlGroup({'login': Control('loginValue')}); }); - var input = fixture.rootElement.querySelector('input') as InputElement; + var input = + fixture.rootElement.querySelector('input') as HTMLInputElement; expect(input.value, 'loginValue'); }); @@ -34,7 +35,8 @@ void main() { component.form = form; }); await fixture.update((_) { - var input = fixture.rootElement.querySelector('input') as InputElement; + var input = + fixture.rootElement.querySelector('input') as HTMLInputElement; input.value = 'updatedValue'; dispatchEvent(input, 'input'); }); @@ -48,7 +50,8 @@ void main() { beforeChangeDetection: (InputFormTest component) { component.form = form; }); - var input = fixture.rootElement.querySelector('input') as InputElement; + var input = + fixture.rootElement.querySelector('input') as HTMLInputElement; form.valueChanges.listen((_) { throw UnsupportedError('should not happen'); }); @@ -81,7 +84,8 @@ void main() { beforeChangeDetection: (SingleControlTest component) { component.form = control; }); - var input = fixture.rootElement.querySelector('input') as InputElement; + var input = + fixture.rootElement.querySelector('input') as HTMLInputElement; expect(input.value, 'loginValue'); await fixture.update((_) { input.value = 'updatedValue'; @@ -100,7 +104,8 @@ void main() { await fixture.update((InputFormTest component) { component.form = ControlGroup({'login': Control('newValue')}); }); - var input = fixture.rootElement.querySelector('input') as InputElement; + var input = + fixture.rootElement.querySelector('input') as HTMLInputElement; expect(input.value, 'newValue'); }); @@ -116,7 +121,8 @@ void main() { await fixture.update((_) { login.updateValue('newValue'); }); - var input = fixture.rootElement.querySelector('input') as InputElement; + var input = + fixture.rootElement.querySelector('input') as HTMLInputElement; expect(input.value, 'newValue'); }); @@ -145,7 +151,8 @@ void main() { beforeChangeDetection: (InputFormTest component) { component.form = form; }); - var input = fixture.rootElement.querySelector('input') as InputElement; + var input = + fixture.rootElement.querySelector('input') as HTMLInputElement; expect(input.value, 'old'); await fixture.update((InputFormTest component) { input.value = 'new'; @@ -162,7 +169,8 @@ void main() { beforeChangeDetection: (InputWithoutTypeTest component) { component.form = form; }); - var input = fixture.rootElement.querySelector('input') as InputElement; + var input = + fixture.rootElement.querySelector('input') as HTMLInputElement; expect(input.value, 'old'); await fixture.update((InputWithoutTypeTest component) { input.value = 'new'; @@ -179,7 +187,7 @@ void main() { component.form = form; }); var textarea = - fixture.rootElement.querySelector('textarea') as TextAreaElement; + fixture.rootElement.querySelector('textarea') as HTMLTextAreaElement; expect(textarea.value, 'old'); await fixture.update((_) { textarea.value = 'new'; @@ -195,7 +203,8 @@ void main() { await testBed.create(beforeChangeDetection: (CheckboxTest component) { component.form = form; }); - var input = fixture.rootElement.querySelector('input') as InputElement; + var input = + fixture.rootElement.querySelector('input') as HTMLInputElement; expect(input.checked, true); await fixture.update((_) { input.checked = false; @@ -211,7 +220,8 @@ void main() { await testBed.create(beforeChangeDetection: (NumberTest component) { component.form = form; }); - var input = fixture.rootElement.querySelector('input') as InputElement; + var input = + fixture.rootElement.querySelector('input') as HTMLInputElement; expect(input.value, '10'); await fixture.update((_) { input.value = '20'; @@ -229,7 +239,8 @@ void main() { beforeChangeDetection: (NumberRequiredTest component) { component.form = form; }); - var input = fixture.rootElement.querySelector('input') as InputElement; + var input = + fixture.rootElement.querySelector('input') as HTMLInputElement; await fixture.update((_) { input.value = ''; dispatchEvent(input, 'input'); @@ -254,7 +265,8 @@ void main() { component.form = form; component.data = ''; }); - var input = fixture.rootElement.querySelector('input') as InputElement; + var input = + fixture.rootElement.querySelector('input') as HTMLInputElement; expect(input.value, ''); }); @@ -268,7 +280,8 @@ void main() { await testBed.create(beforeChangeDetection: (RadioTest component) { component.form = form; }); - var input = fixture.rootElement.querySelector('input') as InputElement; + var input = + fixture.rootElement.querySelector('input') as HTMLInputElement; expect(input.checked, false); await fixture.update((_) { dispatchEvent(input, 'change'); @@ -284,9 +297,9 @@ void main() { NgTestBed(ng.createBasicSelectTestFactory()); var fixture = await testBed.create(); var select = - fixture.rootElement.querySelector('select') as SelectElement; + fixture.rootElement.querySelector('select') as HTMLSelectElement; var sfOption = - fixture.rootElement.querySelector('option') as OptionElement; + fixture.rootElement.querySelector('option') as HTMLOptionElement; expect(select.value, 'SF'); expect(sfOption.selected, true); }); @@ -295,7 +308,7 @@ void main() { var testBed = NgTestBed(ng.createSelectForTestFactory()); var fixture = await testBed.create(); var sfOption = - fixture.rootElement.querySelector('option') as OptionElement; + fixture.rootElement.querySelector('option') as HTMLOptionElement; expect(sfOption.value, '0'); await fixture.update((SelectForTest component) { component.cities.first['id'] = '2'; @@ -312,9 +325,9 @@ void main() { component.form = form; }); var select = - fixture.rootElement.querySelector('select') as SelectElement; + fixture.rootElement.querySelector('select') as HTMLSelectElement; var sfOption = - fixture.rootElement.querySelector('option') as OptionElement; + fixture.rootElement.querySelector('option') as HTMLOptionElement; expect(select.value, 'SF'); expect(sfOption.selected, true); await fixture.update((_) { @@ -330,7 +343,7 @@ void main() { ng.createSelectControlDynamicDataTestFactory()); var fixture = await testBed.create(); var select = - fixture.rootElement.querySelector('select') as SelectElement; + fixture.rootElement.querySelector('select') as HTMLSelectElement; expect(select.value, 'NYC'); }); @@ -342,9 +355,9 @@ void main() { component.selectedCity = component.cities[1]; }); var select = - fixture.rootElement.querySelector('select') as SelectElement; - var nycOption = - fixture.rootElement.querySelectorAll('option')[1] as OptionElement; + fixture.rootElement.querySelector('select') as HTMLSelectElement; + var nycOption = fixture.rootElement.querySelectorAll('option').item(1) + as HTMLOptionElement; expect(select.value, '1: Object'); expect(nycOption.selected, true); await fixture.update((_) { @@ -372,9 +385,9 @@ void main() { component.selectedCity = component.cities[2]; }); var select = - fixture.rootElement.querySelector('select') as SelectElement; - var buffalo = - fixture.rootElement.querySelectorAll('option')[2] as OptionElement; + fixture.rootElement.querySelector('select') as HTMLSelectElement; + var buffalo = fixture.rootElement.querySelectorAll('option').item(2) + as HTMLOptionElement; expect(select.value, '2: Object'); expect(buffalo.selected, true); }); @@ -391,7 +404,7 @@ void main() { component.selectedCity = component.cities[1]; }); var select = - fixture.rootElement.querySelector('select') as SelectElement; + fixture.rootElement.querySelector('select') as HTMLSelectElement; expect(select.value, '1: Object'); await fixture.update((SelectOptionValueMapTest component) { component.cities.removeLast(); @@ -412,9 +425,9 @@ void main() { component.selectedCity = component.cities[1]; }); var select = - fixture.rootElement.querySelector('select') as SelectElement; - var buffalo = - fixture.rootElement.querySelectorAll('option')[1] as OptionElement; + fixture.rootElement.querySelector('select') as HTMLSelectElement; + var buffalo = fixture.rootElement.querySelectorAll('option').item(1) + as HTMLOptionElement; expect(select.value, '1: Buffalo'); expect(buffalo.selected, true); }); @@ -435,9 +448,9 @@ void main() { component.selectedCity = component.cities[1]; }); var select = - fixture.rootElement.querySelector('select') as SelectElement; - var firstSF = - fixture.rootElement.querySelectorAll('option')[1] as OptionElement; + fixture.rootElement.querySelector('select') as HTMLSelectElement; + var firstSF = fixture.rootElement.querySelectorAll('option').item(1) + as HTMLOptionElement; expect(select.value, '1: Object'); expect(firstSF.selected, true); }); @@ -459,9 +472,9 @@ void main() { component.selectedCity = component.cities[2]; }); var select = - fixture.rootElement.querySelector('select') as SelectElement; - var secondNYC = - fixture.rootElement.querySelectorAll('option')[2] as OptionElement; + fixture.rootElement.querySelector('select') as HTMLSelectElement; + var secondNYC = fixture.rootElement.querySelectorAll('option').item(2) + as HTMLOptionElement; expect(select.value, '2: Object'); expect(secondNYC.selected, true); }); @@ -475,7 +488,8 @@ void main() { beforeChangeDetection: (CustomAccessorTest component) { component.form = form; }); - var input = fixture.rootElement.querySelector('input') as InputElement; + var input = + fixture.rootElement.querySelector('input') as HTMLInputElement; expect(input.value, '!aa!'); await fixture.update((_) { input.value = '!bb!'; @@ -513,11 +527,11 @@ void main() { component.form = form; }); var required = - fixture.rootElement.querySelector('[required]') as InputElement; + fixture.rootElement.querySelector('[required]') as HTMLInputElement; var minLength = fixture.rootElement.querySelector('[ngControl=min]') - as InputElement; + as HTMLInputElement; var maxLength = fixture.rootElement.querySelector('[ngControl=max]') - as InputElement; + as HTMLInputElement; await fixture.update((_) { required.value = ''; minLength.value = '1'; @@ -551,7 +565,7 @@ void main() { expect(form.valid, true); await fixture.update((_) { var input = - fixture.rootElement.querySelector('input') as InputElement; + fixture.rootElement.querySelector('input') as HTMLInputElement; input.value = ''; dispatchEvent(input, 'input'); }); @@ -564,7 +578,8 @@ void main() { var testBed = NgTestBed(ng.createNestedFormTestFactory()); var fixture = await testBed.create(); - var input = fixture.rootElement.querySelector('input') as InputElement; + var input = + fixture.rootElement.querySelector('input') as HTMLInputElement; expect(input.value, 'value'); }); @@ -572,7 +587,8 @@ void main() { var testBed = NgTestBed(ng.createNestedFormTestFactory()); var fixture = await testBed.create(); - var input = fixture.rootElement.querySelector('input') as InputElement; + var input = + fixture.rootElement.querySelector('input') as HTMLInputElement; late ControlGroup form; await fixture.update((NestedFormTest component) { input.value = 'updatedValue'; @@ -594,7 +610,8 @@ void main() { await fixture.update((ComplexNgModelTest component) { component.name = 'oldValue'; }); - var input = fixture.rootElement.querySelector('input') as InputElement; + var input = + fixture.rootElement.querySelector('input') as HTMLInputElement; expect(input.value, 'oldValue'); late ComplexNgModelTest comp; await fixture.update((ComplexNgModelTest component) { @@ -612,7 +629,8 @@ void main() { await fixture.update((SingleFieldNgModelTest component) { component.name = 'oldValue'; }); - var input = fixture.rootElement.querySelector('input') as InputElement; + var input = + fixture.rootElement.querySelector('input') as HTMLInputElement; expect(input.value, 'oldValue'); late SingleFieldNgModelTest comp; @@ -702,7 +720,8 @@ void main() { await fixture.update((NgModelComplexTest component) { component.name = 'oldValue'; }); - var input = fixture.rootElement.querySelector('input') as InputElement; + var input = + fixture.rootElement.querySelector('input') as HTMLInputElement; expect(input.value, 'oldValue'); late NgModelComplexTest comp; await fixture.update((NgModelComplexTest component) { @@ -720,7 +739,8 @@ void main() { await fixture.update((NgModelSingleFieldTest component) { component.name = 'oldValue'; }); - var input = fixture.rootElement.querySelector('input') as InputElement; + var input = + fixture.rootElement.querySelector('input') as HTMLInputElement; expect(input.value, 'oldValue'); late NgModelSingleFieldTest comp; await fixture.update((NgModelSingleFieldTest component) { @@ -744,7 +764,8 @@ void main() { beforeChangeDetection: (TemplateRadioTest component) { component.data = data; }); - var input = fixture.rootElement.querySelector('input') as InputElement; + var input = + fixture.rootElement.querySelector('input') as HTMLInputElement; expect(input.checked, false); await fixture.update((_) { dispatchEvent(input, 'change'); @@ -766,7 +787,8 @@ void main() { var testBed = NgTestBed( ng.createNgModelInitialViewTestFactory()); var fixture = await testBed.create(); - var input = fixture.rootElement.querySelector('input') as InputElement; + var input = + fixture.rootElement.querySelector('input') as HTMLInputElement; await fixture.update((_) { input.value = 'aa'; input.selectionStart = 1; @@ -784,7 +806,8 @@ void main() { await fixture.update((NgModelRevertViewTest component) { component.name = ''; }); - var input = fixture.rootElement.querySelector('input') as InputElement; + var input = + fixture.rootElement.querySelector('input') as HTMLInputElement; late NgModelRevertViewTest comp; await fixture.update((NgModelRevertViewTest component) { input.value = 'aa'; diff --git a/ngforms/test/ng_control_group_test.dart b/ngforms/test/ng_control_group_test.dart index de85190fb3..3c12ab4f1d 100644 --- a/ngforms/test/ng_control_group_test.dart +++ b/ngforms/test/ng_control_group_test.dart @@ -1,9 +1,8 @@ -import 'dart:html'; - import 'package:ngdart/angular.dart'; import 'package:ngforms/ngforms.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'ng_control_group_test.template.dart' as ng; @@ -65,7 +64,7 @@ class NgControlGroupTest { NgControlGroup? controlGroup; @ViewChild('input') - InputElement? inputElement; + HTMLInputElement? inputElement; bool disabled = false; diff --git a/ngforms/test/ng_control_name_test.dart b/ngforms/test/ng_control_name_test.dart index 8d09a19fa2..17dc68f164 100644 --- a/ngforms/test/ng_control_name_test.dart +++ b/ngforms/test/ng_control_name_test.dart @@ -1,9 +1,8 @@ -import 'dart:html'; - import 'package:ngdart/angular.dart'; import 'package:ngforms/ngforms.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'ng_control_name_test.template.dart' as ng; @@ -91,7 +90,7 @@ class NgControlNameTest { NgControlName? controlName; @ViewChild('input') - InputElement? inputElement; + HTMLInputElement? inputElement; String? loginValue; diff --git a/ngforms/test/ng_control_repeated_test.dart b/ngforms/test/ng_control_repeated_test.dart index d68a4f6595..706a21f31b 100644 --- a/ngforms/test/ng_control_repeated_test.dart +++ b/ngforms/test/ng_control_repeated_test.dart @@ -14,7 +14,7 @@ void main() { test('should update an NgForm without throwing an NPE', () async { final testBed = NgTestBed(ng.createAppComponentFactory()); expect( - (await testBed.create()).rootElement.innerHtml, + (await testBed.create()).rootElement.innerHTML, contains(r''), ); }); diff --git a/ngforms/test/ng_form_control_test.dart b/ngforms/test/ng_form_control_test.dart index 313920b46a..4b29457579 100644 --- a/ngforms/test/ng_form_control_test.dart +++ b/ngforms/test/ng_form_control_test.dart @@ -1,9 +1,8 @@ -import 'dart:html'; - import 'package:ngdart/angular.dart'; import 'package:ngforms/ngforms.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'ng_form_control_test.template.dart' as ng; @@ -99,7 +98,7 @@ class NgFormControlTest { NgFormControl? formControl; @ViewChild('input') - InputElement? inputElement; + HTMLInputElement? inputElement; Control loginControl = Control(null); } diff --git a/ngforms/test/ng_form_test.dart b/ngforms/test/ng_form_test.dart index 09753e6116..d56949611c 100644 --- a/ngforms/test/ng_form_test.dart +++ b/ngforms/test/ng_form_test.dart @@ -1,9 +1,8 @@ -import 'dart:html'; - import 'package:ngdart/angular.dart'; import 'package:ngforms/ngforms.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'ng_form_test.template.dart' as ng; @@ -171,7 +170,7 @@ class NgFormTest { NgControlName? loginControlDir; @ViewChild('input') - InputElement? inputElement; + HTMLInputElement? inputElement; bool disabled = false; bool needsLogin = true; @@ -199,7 +198,7 @@ class OnPushControlTest { var requiresName = false; @ViewChild('submit') - ButtonElement? submitButton; + HTMLButtonElement? submitButton; } @Component( @@ -224,5 +223,5 @@ class OnPushControlGroupTest { var requiresGroup = false; @ViewChild('submit') - ButtonElement? submitButton; + HTMLButtonElement? submitButton; } From 527e0ef15708dd9e3f8cdeedd62731d6d73af17c Mon Sep 17 00:00:00 2001 From: Olzhas Suleimen Date: Tue, 22 Apr 2025 18:52:27 +0500 Subject: [PATCH 06/19] Update ngcompiler, ngdart, ngforms. Signed-off-by: Gavin Zhao --- ngcompiler/lib/v1/src/compiler/identifiers.dart | 14 ++++++++++---- .../view_compiler/update_statement_visitor.dart | 13 ++++++++----- ngcompiler/lib/v2/context.dart | 3 +-- .../lib/src/core/linker/views/render_view.dart | 15 +++++++++------ ngdart/lib/src/runtime/dom_helpers.dart | 17 +++++++++++++++-- .../radio_control_value_accessor.dart | 3 ++- 6 files changed, 45 insertions(+), 20 deletions(-) diff --git a/ngcompiler/lib/v1/src/compiler/identifiers.dart b/ngcompiler/lib/v1/src/compiler/identifiers.dart index ac0003e65c..4b510d5516 100644 --- a/ngcompiler/lib/v1/src/compiler/identifiers.dart +++ b/ngcompiler/lib/v1/src/compiler/identifiers.dart @@ -39,7 +39,8 @@ class JsInterop { ); } - static final stringToJSString = _of('StringToJSString'); + static final functionToJSExportedDartFunction = + _of('FunctionToJSExportedDartFunction'); } /// A collection of methods for manipulating the DOM from generated code. @@ -288,6 +289,8 @@ class Identifiers { // Runtime is initialized by output interpreter. Compiler executes in VM and // can't import `package:web/web.dart` to initialize here. + static var event = CompileIdentifierMetadata( + name: 'Event', moduleUrl: 'asset:web/lib/src/dom/dom.dart'); static var commentNode = CompileIdentifierMetadata( name: 'Comment', moduleUrl: 'asset:web/lib/src/dom/dom.dart'); static var textNode = CompileIdentifierMetadata( @@ -327,7 +330,8 @@ class Identifiers { static final inputElement = CompileIdentifierMetadata( name: 'HTMLInputElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final textareaElement = CompileIdentifierMetadata( - name: 'HTMLTextAreaElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); + name: 'HTMLTextAreaElement', + moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final mediaElement = CompileIdentifierMetadata( name: 'HTMLMediaElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final menuElement = CompileIdentifierMetadata( @@ -341,9 +345,11 @@ class Identifiers { static final tableElement = CompileIdentifierMetadata( name: 'HTMLTableElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final tableRowElement = CompileIdentifierMetadata( - name: 'HTMLTableRowElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); + name: 'HTMLTableRowElement', + moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final tableColElement = CompileIdentifierMetadata( - name: 'HTMLTableColElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); + name: 'HTMLTableColElement', + moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final uListElement = CompileIdentifierMetadata( name: 'HTMLUListElement', moduleUrl: 'asset:web/lib/src/dom/html.dart'); static final node = CompileIdentifierMetadata( diff --git a/ngcompiler/lib/v1/src/compiler/view_compiler/update_statement_visitor.dart b/ngcompiler/lib/v1/src/compiler/view_compiler/update_statement_visitor.dart index c80403a476..b97124afbc 100644 --- a/ngcompiler/lib/v1/src/compiler/view_compiler/update_statement_visitor.dart +++ b/ngcompiler/lib/v1/src/compiler/view_compiler/update_statement_visitor.dart @@ -124,10 +124,7 @@ class _UpdateStatementsVisitor return o.importExpr(DomHelpers.setProperty).callFn([ renderNode!.toReadExpr(), o.literal(propertyBinding.name), - o - // TODO(ykmnkmi): math other types - .importExpr(JsInterop.stringToJSString) - .callFn([renderValue!]).prop('toJS'), + renderValue!, ]).toStmt(); } @@ -248,7 +245,13 @@ class _UpdateStatementsVisitor [o.Expression? renderValue]) => (renderNode?.toReadExpr() ?? appViewInstance!).callMethod( 'addEventListener', - [o.literal(nativeEvent.name), renderValue!], + [ + o.literal(nativeEvent.name), + o.importExpr(JsInterop.functionToJSExportedDartFunction).instantiate([ + renderValue!.cast( + o.FunctionType(o.voidType, [o.importType(Identifiers.event)!])) + ]).prop('toJS') + ], ).toStmt(); } diff --git a/ngcompiler/lib/v2/context.dart b/ngcompiler/lib/v2/context.dart index e6e62bf423..04b2d309c6 100644 --- a/ngcompiler/lib/v2/context.dart +++ b/ngcompiler/lib/v2/context.dart @@ -71,8 +71,7 @@ Future runWithContext( ); } if (!buildCompletedOrFailed.isCompleted) { - // TODO(ykmnkmi): check or switch back to complete. - buildCompletedOrFailed.completeError(e, s); + buildCompletedOrFailed.complete(); } }, zoneSpecification: ZoneSpecification( diff --git a/ngdart/lib/src/core/linker/views/render_view.dart b/ngdart/lib/src/core/linker/views/render_view.dart index ef068ff317..814626f9ea 100644 --- a/ngdart/lib/src/core/linker/views/render_view.dart +++ b/ngdart/lib/src/core/linker/views/render_view.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:js_interop'; import 'package:meta/dart2js.dart' as dart2js; import 'package:ngdart/src/core/linker/app_view_utils.dart'; @@ -129,11 +128,11 @@ abstract class RenderView extends View { /// * Calls [markForCheck] on this view to ensure it gets change detected /// during the next change detection cycle, in case it uses a non-default /// change detection strategy. - JSFunction eventHandler0(void Function() handler) { + void Function(Event event) eventHandler0(void Function() handler) { return (Event event) { markForCheck(); appViewUtils.eventManager.zone.runGuarded(handler); - }.toJS; + }; } /// The same as [eventHandler0], but [handler] is passed an event parameter. @@ -150,13 +149,17 @@ abstract class RenderView extends View { /// of the event listener is a subclass of [Event]. The [Event] passed in from /// [EventTarget.addEventListener] can then be safely coerced back to its /// known type. - JSFunction eventHandler1(void Function(E event) handler) { + void Function(E) eventHandler1(void Function(F) handler) { + assert( + E == Null || F != Null, + "Event handler '$handler' isn't assignable to expected type " + "'($E) => void'"); return (E event) { markForCheck(); appViewUtils.eventManager.zone.runGuarded( - () => handler(event), + () => handler(unsafeCast(event)), ); - }.toJS; + }; } // Styling ------------------------------------------------------------------- diff --git a/ngdart/lib/src/runtime/dom_helpers.dart b/ngdart/lib/src/runtime/dom_helpers.dart index b87ba7982f..97870d74d9 100644 --- a/ngdart/lib/src/runtime/dom_helpers.dart +++ b/ngdart/lib/src/runtime/dom_helpers.dart @@ -123,9 +123,22 @@ void setAttribute( void setProperty( Element element, String property, - JSAny? value, + Object? value, ) { - element[property] = value; + // TODO(ykmnkmi): `ngcompiler` doesn't have type data to use convert + // values to JS types and use `JSAny` here. Expected to be inlined + // with right type. + if (value == null) { + element[property] = null; + } else if (value is bool) { + element[property] = value.toJS; + } else if (value is num) { + element[property] = value.toJS; + } else if (value is String) { + element[property] = value.toJS; + } else { + element[property] = value.jsify(); + } } /// Creates a [Text] node with the provided [contents]. diff --git a/ngforms/lib/src/directives/radio_control_value_accessor.dart b/ngforms/lib/src/directives/radio_control_value_accessor.dart index 88b64b5119..ba604a7958 100644 --- a/ngforms/lib/src/directives/radio_control_value_accessor.dart +++ b/ngforms/lib/src/directives/radio_control_value_accessor.dart @@ -86,7 +86,8 @@ class RadioControlValueAccessor extends Object @Input() String? name; - RadioControlValueAccessor(this._element, this._registry, this._injector); + RadioControlValueAccessor(HTMLElement element, this._registry, this._injector) + : _element = element as HTMLInputElement; @HostListener('change') void changeHandler() { From 047ff40a04aaa5edc2af9a2e12259997427c7102 Mon Sep 17 00:00:00 2001 From: Olzhas Suleimen Date: Tue, 22 Apr 2025 22:21:49 +0500 Subject: [PATCH 07/19] Migrating _tests. Update ngdart. Signed-off-by: Gavin Zhao --- _tests/lib/compiler.dart | 2 +- _tests/lib/matchers.dart | 109 ++++++++++--- _tests/pubspec.yaml | 2 +- _tests/test/bootstrap/run_app_test.dart | 30 ++-- _tests/test/common/directives/for_test.dart | 12 +- _tests/test/common/directives/if_test.dart | 21 +-- .../test/common/directives/ng_class_test.dart | 143 ++++++++++-------- .../test/common/directives/ng_style_test.dart | 9 +- _tests/test/common/pipes/async_pipe_test.dart | 2 +- .../expression_parser/analyzer_test.dart | 2 +- .../invalid_late_fields_test.dart | 12 +- _tests/test/core/application_ref_test.dart | 13 +- .../detect_host_changes_test.dart | 9 +- .../differs/default_iterable_differ_test.dart | 2 - .../on_push_embedded_view_test.dart | 20 +-- .../test/core/directive_inheritance_test.dart | 2 +- .../directive_lifecycle_integration_test.dart | 2 - _tests/test/core/event_handler_test.dart | 50 +++--- _tests/test/core/exports_test.dart | 11 +- _tests/test/core/host_annotation_test.dart | 19 +-- _tests/test/core/i18n_escape_test.dart | 5 +- _tests/test/core/i18n_test.dart | 9 +- .../core/linker/component_loader_test.dart | 4 +- .../core/linker/component_selector_test.dart | 81 ++++++---- .../core/linker/implicit_static_test.dart | 5 +- .../integration/binding_integration_test.dart | 23 +-- .../directive_integration_test.dart | 6 +- .../core/linker/integration/misc_test.dart | 10 +- .../linker/integration/ng_container_test.dart | 48 +++--- .../core/linker/integration/on_push_test.dart | 14 +- .../core/linker/integration/outputs_test.dart | 10 +- .../integration/reference_binding_test.dart | 5 +- .../linker/integration/template_test.dart | 13 +- .../integration/view_creation_test.dart | 44 +++--- .../core/linker/query_integration_test.dart | 11 +- .../linker/security_integration_test.dart | 28 ++-- .../core/linker/style_encapsulation_test.dart | 23 ++- _tests/test/core/query_html_element_test.dart | 31 ++-- .../styling/clear_component_styles_test.dart | 4 +- _tests/test/core/styling/order_test.dart | 5 +- _tests/test/core/styling/shim_test.dart | 9 +- .../view/projection_integration_test.dart | 25 +-- _tests/test/core/view/projection_test.dart | 135 +++++++++++++---- .../core/view/view_child_regression_test.dart | 3 +- _tests/test/devtools/inspector_test.dart | 23 ++- .../ng_for_content_projection_test.dart | 43 +++--- _tests/test/integration/query_view_test.dart | 6 +- .../platform/dom/events/key_events_test.dart | 29 ++-- .../dom_sanitization_service_test.dart | 1 + _tests/test/security/html_sanitizer_test.dart | 1 + .../test/security/safe_inner_html_test.dart | 3 +- .../test/security/style_sanitizer_test.dart | 1 + _tests/test/security/url_sanitizer_test.dart | 1 + .../templates/additional_expression_lib.dart | 3 - .../templates/additional_expression_test.dart | 16 +- .../update_statement_visitor.dart | 2 +- .../src/core/linker/views/render_view.dart | 4 +- 57 files changed, 690 insertions(+), 466 deletions(-) diff --git a/_tests/lib/compiler.dart b/_tests/lib/compiler.dart index e011bf78a6..e8c6033bfc 100644 --- a/_tests/lib/compiler.dart +++ b/_tests/lib/compiler.dart @@ -106,7 +106,7 @@ Future _testBuilder( inputIds, reader, writer, - AnalyzerResolvers(), + AnalyzerResolvers.custom(), logger: logger, ), ['non-nullable'], diff --git a/_tests/lib/matchers.dart b/_tests/lib/matchers.dart index 9309be769f..9e528605f5 100644 --- a/_tests/lib/matchers.dart +++ b/_tests/lib/matchers.dart @@ -1,11 +1,18 @@ -import 'dart:html'; +import 'dart:js_interop'; import 'package:ngdart/angular.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; /// Matches textual content of an element including children. Matcher hasTextContent(String expected) => _HasTextContent(expected); +/// Matches DOMTokenList. +Matcher hasDomTokenList(List expected) => _HasDomTokenList(expected); + +/// Matches textual content of an element including children. +Matcher hasInnerHtml(String expected) => _HasInnerHtml(expected); + final throwsNoProviderError = throwsA(_isNoProviderError); final _isNoProviderError = const TypeMatcher(); @@ -15,7 +22,8 @@ class _HasTextContent extends Matcher { const _HasTextContent(this.expectedText); @override - bool matches(Object? item, void _) => _elementText(item) == expectedText; + bool matches(Object? item, void _) => + _elementText(item as JSAny?) == expectedText; @override Description describe(Description description) => @@ -29,33 +37,100 @@ class _HasTextContent extends Matcher { void __, ) { mismatchDescription.add('Text content of element: ' - '\'${_elementText(item)}\''); + '\'${_elementText(item as JSAny?)}\''); return mismatchDescription; } } -String? _elementText(Object? n) { - if (n is Iterable) { - return n.map(_elementText).join(''); - } else if (n is Node) { - if (n is Comment) { +String? _elementText(JSAny? node) { + if (node.isA()) { + return [ + for (var i = 0; i < (node as NodeList).length; i++) + _elementText(node.item(i)) + ].join(''); + } + + if (node.isA()) { + if (node.isA()) { return ''; } - if (n is ContentElement) { - return _elementText(n.getDistributedNodes()); + if (node.isA() && (node as Element).shadowRoot != null) { + return _elementText(node.shadowRoot!.childNodes); + } + + if ((node as Node).childNodes.length != 0) { + return _elementText(node.childNodes); } - if (n is Element && n.shadowRoot != null) { - return _elementText(n.shadowRoot!.nodes); + return node.textContent; + } + + return null; +} + +class _HasDomTokenList extends Matcher { + final List expectedTokens; + + const _HasDomTokenList(this.expectedTokens); + + @override + bool matches(Object? item, void _) { + final tokens = item as DOMTokenList; + + if (tokens.length != expectedTokens.length) { + return false; } - if (n.nodes.isNotEmpty) { - return _elementText(n.nodes); + for (var i = 0; i < expectedTokens.length; i++) { + if (tokens.item(i) != expectedTokens[i]) { + return false; + } } - return n.text; - } else { - return '$n'; + return true; + } + + @override + Description describe(Description description) { + return description.add(expectedTokens.join(',')); + } + + @override + Description describeMismatch( + item, + Description mismatchDescription, + void _, + void __, + ) { + mismatchDescription.add('DOMTokenList: \'$item}\''); + return mismatchDescription; + } +} + +class _HasInnerHtml extends Matcher { + final String expectedHtml; + + const _HasInnerHtml(this.expectedHtml); + + @override + bool matches(Object? item, void _) { + return ((item as Element).innerHTML as JSString).toDart == expectedHtml; + } + + @override + Description describe(Description description) => + description.add(expectedHtml); + + @override + Description describeMismatch( + item, + Description mismatchDescription, + void _, + void __, + ) { + mismatchDescription.add('Inner HTML of element: ' + '\'${_elementText(item as Element)}\''); + return mismatchDescription; } } diff --git a/_tests/pubspec.yaml b/_tests/pubspec.yaml index 21afcfec53..fefc46af6e 100644 --- a/_tests/pubspec.yaml +++ b/_tests/pubspec.yaml @@ -13,13 +13,13 @@ dependencies: build_test: ^2.2.2 collection: ^1.19.1 glob: ^2.1.2 - js: ^0.7.1 logging: ^1.3.0 ngcompiler: ^3.0.0-dev.3 ngdart: ^8.0.0-dev.4 ngtest: ^5.0.0-dev.2 source_gen: ^1.5.0 test: ^1.25.9 + web: ^1.1.1 dev_dependencies: analyzer: ^6.5.0 diff --git a/_tests/test/bootstrap/run_app_test.dart b/_tests/test/bootstrap/run_app_test.dart index fcee2b14f2..e76876e662 100644 --- a/_tests/test/bootstrap/run_app_test.dart +++ b/_tests/test/bootstrap/run_app_test.dart @@ -1,12 +1,9 @@ -@JS() -library angular.test.bootstrap.run_app_test; - import 'dart:async'; -import 'dart:html'; +import 'dart:js_interop'; -import 'package:js/js.dart'; import 'package:ngdart/angular.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'run_app_test.template.dart' as ng; @@ -27,9 +24,9 @@ void main() { /// Verify that the DOM of the page represents the component. void verifyDomAndStyles({String innerText = 'Hello World!'}) { - expect(rootDomContainer.text, innerText); + expect(rootDomContainer.textContent, innerText); final h1 = rootDomContainer.querySelector('h1'); - expect(h1!.getComputedStyle().height, '100px'); + expect(window.getComputedStyle(h1!).height, '100px'); } /// Verify the `Testability` interface is working for this application. @@ -38,21 +35,21 @@ void main() { void verifyTestability() { expect(component.injector.get(Testability), isNotNull); var jsTestability = getAngularTestability( - rootDomContainer.children.first, + rootDomContainer.children.item(0)!, ); - expect(getAllAngularTestabilities(), isNot(hasLength(0))); + expect(getAllAngularTestabilities().length, isNot(equals(0))); expect(jsTestability.isStable(), isTrue, reason: 'Expected stability'); - jsTestability.whenStable(allowInterop(expectAsync0(() { + jsTestability.whenStable(expectAsync0(() { Future(expectAsync0(() { verifyDomAndStyles(innerText: 'Hello Universe!'); })); - }))); + }).toJS); runInApp(() => HelloWorldComponent.doAsyncTaskAndThenRename('Universe')); } setUp(() { - rootDomContainer = DivElement()..id = 'test-root-dom'; - rootDomContainer.append(Element.tag('hello-world')); + rootDomContainer = HTMLDivElement()..id = 'test-root-dom'; + rootDomContainer.append(document.createElement('hello-world')); document.body!.append(rootDomContainer); HelloWorldComponent.name = 'World'; }); @@ -166,10 +163,9 @@ class StubExceptionHandler implements ExceptionHandler { external JsTestability getAngularTestability(Element e); @JS() -external List getAllAngularTestabilities(); +external JSArray getAllAngularTestabilities(); -@JS() -abstract class JsTestability { +extension type JsTestability._(JSObject _) implements JSObject { external bool isStable(); - external void whenStable(void Function() fn); + external void whenStable(JSFunction fn); } diff --git a/_tests/test/common/directives/for_test.dart b/_tests/test/common/directives/for_test.dart index b881721920..ab83eb5e4b 100644 --- a/_tests/test/common/directives/for_test.dart +++ b/_tests/test/common/directives/for_test.dart @@ -1,5 +1,3 @@ -library angular2.test.common.directives.for_test; - import 'dart:async'; import 'package:_tests/matchers.dart'; @@ -298,7 +296,7 @@ void main() { await testFixture.update((component) { component.child!.items = ['a', 'b', 'c']; }); - expect(testFixture.text, hasTextContent('0: a;1: b;2: c;')); + expect(testFixture.text, equals('0: a;1: b;2: c;')); }); test('should use a default template if a custom one is null', () async { @@ -308,7 +306,7 @@ void main() { await testFixture.update((NgForCustomTemplateNullTest component) { component.child!.items = ['a', 'b', 'c']; }); - expect(testFixture.text, hasTextContent('0: a;1: b;2: c;')); + expect(testFixture.text, equals('0: a;1: b;2: c;')); }); test( @@ -320,7 +318,7 @@ void main() { await testFixture.update((NgForCustomTemplatePrecedenceTest component) { component.child!.items = ['a', 'b', 'c']; }); - expect(testFixture.text, hasTextContent('0: a;1: b;2: c;')); + expect(testFixture.text, equals('0: a;1: b;2: c;')); }); group('track by', () { @@ -380,8 +378,8 @@ void main() { ]; }); var endElements = testFixture.rootElement.querySelectorAll('p'); - expect(startElements[0], endElements[1]); - expect(startElements[1], endElements[0]); + expect(startElements.item(0), endElements.item(1)); + expect(startElements.item(1), endElements.item(0)); }); test( diff --git a/_tests/test/common/directives/if_test.dart b/_tests/test/common/directives/if_test.dart index 956a8b7028..ef4122c5e6 100644 --- a/_tests/test/common/directives/if_test.dart +++ b/_tests/test/common/directives/if_test.dart @@ -1,3 +1,5 @@ +import 'dart:js_interop'; + import 'package:ngdart/angular.dart'; import 'package:ngdart/src/runtime/check_binding.dart'; import 'package:ngtest/angular_test.dart'; @@ -15,7 +17,7 @@ void main() { var testFixture = await testBed.create(); var element = testFixture.rootElement; expect(element.querySelectorAll('copy-me'), hasLength(1)); - expect(element.innerHtml, contains('hello2')); + expect(element.innerHTML, contains('hello2')); }); test('should toggle node when condition changes', () async { @@ -50,31 +52,31 @@ void main() { component.booleanCondition = false; }); expect(element.querySelectorAll('copy-me'), hasLength(0)); - expect(element.innerHtml!.contains('hello'), false); + expect((element.innerHTML as JSString).toDart.contains('hello'), isFalse); await testFixture.update((NgIfNestedTestComponent component) { component.booleanCondition = true; }); expect(element.querySelectorAll('copy-me'), hasLength(1)); - expect(element.innerHtml!.contains('hello'), true); + expect((element.innerHTML as JSString).toDart.contains('hello'), isTrue); await testFixture.update((NgIfNestedTestComponent component) { component.nestedBooleanCondition = false; }); expect(element.querySelectorAll('copy-me'), hasLength(0)); - expect(element.innerHtml!.contains('hello'), false); + expect((element.innerHTML as JSString).toDart.contains('hello'), isFalse); await testFixture.update((NgIfNestedTestComponent component) { component.nestedBooleanCondition = true; }); expect(element.querySelectorAll('copy-me'), hasLength(1)); - expect(element.innerHtml!.contains('hello'), true); + expect((element.innerHTML as JSString).toDart.contains('hello'), isTrue); await testFixture.update((NgIfNestedTestComponent component) { component.booleanCondition = false; }); expect(element.querySelectorAll('copy-me'), hasLength(0)); - expect(element.innerHtml!.contains('hello'), false); + expect((element.innerHTML as JSString).toDart.contains('hello'), isFalse); }); test('should update multiple bindings', () async { @@ -84,20 +86,21 @@ void main() { var element = testFixture.rootElement; // Check startup. expect(element.querySelectorAll('copy-me'), hasLength(3)); - expect(element.text, 'helloNumberhelloStringhelloFunction'); + expect( + element.textContent, equals('helloNumberhelloStringhelloFunction')); await testFixture.update((NgIfMultiUpdateTestComponent component) { component.numberCondition = 0; }); expect(element.querySelectorAll('copy-me'), hasLength(1)); - expect(element.text, 'helloString'); + expect(element.textContent, equals('helloString')); await testFixture.update((NgIfMultiUpdateTestComponent component) { component.numberCondition = 1; component.stringCondition = 'bar'; }); expect(element.querySelectorAll('copy-me'), hasLength(1)); - expect(element.text, 'helloNumber'); + expect(element.textContent, equals('helloNumber')); await testFixture.update((NgIfMultiUpdateTestComponent component) { component.booleanCondition = false; }); diff --git a/_tests/test/common/directives/ng_class_test.dart b/_tests/test/common/directives/ng_class_test.dart index 1692a5753b..157100a359 100644 --- a/_tests/test/common/directives/ng_class_test.dart +++ b/_tests/test/common/directives/ng_class_test.dart @@ -1,8 +1,8 @@ -import 'dart:html'; - +import 'package:_tests/matchers.dart'; import 'package:ngdart/angular.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'ng_class_test.template.dart' as ng; @@ -25,8 +25,8 @@ void main() { ]; }); expect( - testFixture.rootElement.querySelector('div')!.classes, - equals(['1']), + testFixture.rootElement.querySelector('div')!.classList, + hasDomTokenList(['1']), ); }); @@ -35,8 +35,8 @@ void main() { var testBed = NgTestBed(ng.createClassWithNamesFactory()); var testFixture = await testBed.create(); expect( - testFixture.rootElement.querySelector('div')!.classes, - equals(['foo-bar', 'fooBar']), + testFixture.rootElement.querySelector('div')!.classList, + hasDomTokenList(['foo-bar', 'fooBar']), ); }); @@ -45,30 +45,30 @@ void main() { NgTestBed(ng.createConditionMapTestFactory()); var testFixture = await testBed.create(); var content = testFixture.rootElement.querySelector('div')!; - expect(content.classes, equals(['foo'])); + expect(content.classList, hasDomTokenList(['foo'])); await testFixture.update((ConditionMapTest component) { component.condition = false; }); - expect(content.classes, equals(['bar'])); + expect(content.classList, hasDomTokenList(['bar'])); }); test('should update classes based on changes to the map', () async { var testBed = NgTestBed(ng.createMapUpdateTestFactory()); var testFixture = await testBed.create(); var content = testFixture.rootElement.querySelector('div')!; - expect(content.classes, equals(['foo'])); + expect(content.classList, hasDomTokenList(['foo'])); await testFixture.update((MapUpdateTest component) { component.map!['bar'] = true; }); - expect(content.classes, equals(['foo', 'bar'])); + expect(content.classList, hasDomTokenList(['foo', 'bar'])); await testFixture.update((MapUpdateTest component) { component.map!['baz'] = true; }); - expect(content.classes, equals(['foo', 'bar', 'baz'])); + expect(content.classList, hasDomTokenList(['foo', 'bar', 'baz'])); await testFixture.update((MapUpdateTest component) { component.map!.remove('bar'); }); - expect(content.classes, equals(['foo', 'baz'])); + expect(content.classList, hasDomTokenList(['foo', 'baz'])); }); test('should update classes based on reference changes to the map', @@ -76,30 +76,30 @@ void main() { var testBed = NgTestBed(ng.createMapUpdateTestFactory()); var testFixture = await testBed.create(); var content = testFixture.rootElement.querySelector('div')!; - expect(content.classes, equals(['foo'])); + expect(content.classList, hasDomTokenList(['foo'])); await testFixture.update((MapUpdateTest component) { component.map = {'foo': true, 'bar': true}; }); - expect(content.classes, equals(['foo', 'bar'])); + expect(content.classList, hasDomTokenList(['foo', 'bar'])); await testFixture.update((MapUpdateTest component) { component.map = {'baz': true}; }); - expect(content.classes, equals(['baz'])); + expect(content.classList, hasDomTokenList(['baz'])); }); test('should remove classes when expression is null', () async { var testBed = NgTestBed(ng.createMapUpdateTestFactory()); var testFixture = await testBed.create(); var content = testFixture.rootElement.querySelector('div')!; - expect(content.classes, equals(['foo'])); + expect(content.classList, hasDomTokenList(['foo'])); await testFixture.update((MapUpdateTest component) { component.map = null; }); - expect(content.classes, isEmpty); + expect(content.classList, isEmpty); await testFixture.update((MapUpdateTest component) { component.map = {'foo': false, 'bar': true}; }); - expect(content.classes, equals(['bar'])); + expect(content.classList, hasDomTokenList(['bar'])); }); test('should allow multiple classes per expression', () async { @@ -109,11 +109,12 @@ void main() { await testFixture.update((MapUpdateTest component) { component.map = {'bar baz': true, 'bar1 baz1': true}; }); - expect(content.classes, equals(['bar', 'baz', 'bar1', 'baz1'])); + expect( + content.classList, hasDomTokenList(['bar', 'baz', 'bar1', 'baz1'])); await testFixture.update((MapUpdateTest component) { component.map = {'bar baz': false, 'bar1 baz1': true}; }); - expect(content.classes, equals(['bar1', 'baz1'])); + expect(content.classList, hasDomTokenList(['bar1', 'baz1'])); }); test('should split by one or more spaces between classes', () async { @@ -123,37 +124,37 @@ void main() { await testFixture.update((MapUpdateTest component) { component.map = {'foo bar baz': true}; }); - expect(content.classes, equals(['foo', 'bar', 'baz'])); + expect(content.classList, hasDomTokenList(['foo', 'bar', 'baz'])); }); test('should update classes based on changes to the list', () async { var testBed = NgTestBed(ng.createListUpdateTestFactory()); var testFixture = await testBed.create(); var content = testFixture.rootElement.querySelector('div')!; - expect(content.classes, equals(['foo'])); + expect(content.classList, hasDomTokenList(['foo'])); await testFixture.update((ListUpdateTest component) { component.list.add('bar'); }); - expect(content.classes, equals(['foo', 'bar'])); + expect(content.classList, hasDomTokenList(['foo', 'bar'])); await testFixture.update((ListUpdateTest component) { component.list[1] = 'baz'; }); - expect(content.classes, equals(['foo', 'baz'])); + expect(content.classList, hasDomTokenList(['foo', 'baz'])); await testFixture.update((ListUpdateTest component) { component.list.remove('baz'); }); - expect(content.classes, equals(['foo'])); + expect(content.classList, hasDomTokenList(['foo'])); }); test('should update classes when list reference changes', () async { var testBed = NgTestBed(ng.createListUpdateTestFactory()); var testFixture = await testBed.create(); var content = testFixture.rootElement.querySelector('div')!; - expect(content.classes, equals(['foo'])); + expect(content.classList, hasDomTokenList(['foo'])); await testFixture.update((ListUpdateTest component) { component.list = ['bar']; }); - expect(content.classes, equals(['bar'])); + expect(content.classList, hasDomTokenList(['bar'])); }); test('should take initial classes into account when a reference changes', @@ -162,11 +163,11 @@ void main() { ng.createListUpdateWithInitialTestFactory()); var testFixture = await testBed.create(); var content = testFixture.rootElement.querySelector('div')!; - expect(content.classes, equals(['foo'])); + expect(content.classList, hasDomTokenList(['foo'])); await testFixture.update((ListUpdateWithInitialTest component) { component.list = ['bar']; }); - expect(content.classes, equals(['foo', 'bar'])); + expect(content.classList, hasDomTokenList(['foo', 'bar'])); }); test('should ignore empty or blank class names', () async { @@ -177,7 +178,7 @@ void main() { await testFixture.update((ListUpdateWithInitialTest component) { component.list = ['', ' ']; }); - expect(content.classes, equals(['foo'])); + expect(content.classList, hasDomTokenList(['foo'])); }); test('should trim blanks from class names', () async { @@ -188,7 +189,7 @@ void main() { await testFixture.update((ListUpdateWithInitialTest component) { component.list = [' bar ']; }); - expect(content.classes, equals(['foo', 'bar'])); + expect(content.classList, hasDomTokenList(['foo', 'bar'])); }); test('should allow multiple classes per item in lists', () async { @@ -198,12 +199,13 @@ void main() { await testFixture.update((ListUpdateTest component) { component.list = ['foo bar baz', 'foo1 bar1 baz1']; }); - expect(content.classes, + expect(content.classList, equals(['foo', 'bar', 'baz', 'foo1', 'bar1', 'baz1'])); await testFixture.update((ListUpdateTest component) { component.list = ['foo bar baz foobar']; }); - expect(content.classes, equals(['foo', 'bar', 'baz', 'foobar'])); + expect( + content.classList, hasDomTokenList(['foo', 'bar', 'baz', 'foobar'])); }); test('should update classes if the set instance changes', () async { @@ -215,13 +217,13 @@ void main() { await testFixture.update((SetUpdateTest component) { component.set = set; }); - expect(content.classes, equals(['bar'])); + expect(content.classList, hasDomTokenList(['bar'])); set = {}; set.add('baz'); await testFixture.update((SetUpdateTest component) { component.set = set; }); - expect(content.classes, equals(['baz'])); + expect(content.classList, hasDomTokenList(['baz'])); }); test('should add classes specified in a string literal', () async { @@ -229,7 +231,8 @@ void main() { NgTestBed(ng.createStringLiteralTestFactory()); var testFixture = await testBed.create(); var content = testFixture.rootElement.querySelector('div')!; - expect(content.classes, equals(['foo', 'bar', 'foo-bar', 'fooBar'])); + expect(content.classList, + hasDomTokenList(['foo', 'bar', 'foo-bar', 'fooBar'])); }); test('should update classes based on changes to the string', () async { @@ -237,15 +240,15 @@ void main() { NgTestBed(ng.createStringUpdateTestFactory()); var testFixture = await testBed.create(); var content = testFixture.rootElement.querySelector('div')!; - expect(content.classes, equals(['foo'])); + expect(content.classList, hasDomTokenList(['foo'])); await testFixture.update((StringUpdateTest component) { component.string = 'foo bar'; }); - expect(content.classes, equals(['foo', 'bar'])); + expect(content.classList, hasDomTokenList(['foo', 'bar'])); await testFixture.update((StringUpdateTest component) { component.string = 'baz'; }); - expect(content.classes, equals(['baz'])); + expect(content.classList, hasDomTokenList(['baz'])); }); test('should remove active classes when switching from string to null', @@ -254,11 +257,11 @@ void main() { NgTestBed(ng.createStringUpdateTestFactory()); var testFixture = await testBed.create(); var content = testFixture.rootElement.querySelector('div')!; - expect(content.classes, equals(['foo'])); + expect(content.classList, hasDomTokenList(['foo'])); await testFixture.update((StringUpdateTest component) { component.string = null; }); - expect(content.classes, isEmpty); + expect(content.classList.length, equals(0)); }); test( @@ -268,11 +271,11 @@ void main() { ng.createStringUpdateWithInitialTestFactory()); var testFixture = await testBed.create(); var content = testFixture.rootElement.querySelector('div')!; - expect(content.classes, equals(['foo'])); + expect(content.classList, hasDomTokenList(['foo'])); await testFixture.update((StringUpdateWithInitialTest component) { component.string = null; }); - expect(content.classes, equals(['foo'])); + expect(content.classList, hasDomTokenList(['foo'])); }); test('should ignore empty and blank strings', () async { @@ -283,7 +286,7 @@ void main() { await testFixture.update((StringUpdateWithInitialTest component) { component.string = ''; }); - expect(content.classes, equals(['foo'])); + expect(content.classList, hasDomTokenList(['foo'])); }); test('should cooperate with the class attribute', () async { @@ -294,15 +297,15 @@ void main() { await testFixture.update((MapUpdateWithInitialTest component) { component.map!['bar'] = true; }); - expect(content.classes, equals(['init', 'foo', 'bar'])); + expect(content.classList, hasDomTokenList(['init', 'foo', 'bar'])); await testFixture.update((MapUpdateWithInitialTest component) { component.map!['foo'] = false; }); - expect(content.classes, equals(['init', 'bar'])); + expect(content.classList, hasDomTokenList(['init', 'bar'])); await testFixture.update((MapUpdateWithInitialTest component) { component.map = null; }); - expect(content.classes, equals(['init', 'foo'])); + expect(content.classList, hasDomTokenList(['init', 'foo'])); }); test('should cooperate with interpolated class attribute', () async { @@ -314,17 +317,17 @@ void main() { .update((MapUpdateWithInitialInterpolationTest component) { component.map!['bar'] = true; }); - expect(content.classes, equals(['init', 'foo', 'bar'])); + expect(content.classList, hasDomTokenList(['init', 'foo', 'bar'])); await testFixture .update((MapUpdateWithInitialInterpolationTest component) { component.map!['foo'] = false; }); - expect(content.classes, equals(['init', 'bar'])); + expect(content.classList, hasDomTokenList(['init', 'bar'])); await testFixture .update((MapUpdateWithInitialInterpolationTest component) { component.map = null; }); - expect(content.classes, equals(['init', 'foo'])); + expect(content.classList, hasDomTokenList(['init', 'foo'])); }); test('should cooperate with class attribute and binding to it', () async { @@ -335,15 +338,15 @@ void main() { await testFixture.update((MapUpdateWithInitialBindingTest component) { component.map!['bar'] = true; }); - expect(content.classes, equals(['init', 'foo', 'bar'])); + expect(content.classList, hasDomTokenList(['init', 'foo', 'bar'])); await testFixture.update((MapUpdateWithInitialBindingTest component) { component.map!['foo'] = false; }); - expect(content.classes, equals(['init', 'bar'])); + expect(content.classList, hasDomTokenList(['init', 'bar'])); await testFixture.update((MapUpdateWithInitialBindingTest component) { component.map = null; }); - expect(content.classes, equals(['init', 'foo'])); + expect(content.classList, hasDomTokenList(['init', 'foo'])); }); test('should cooperate with class attribute and class.name binding', @@ -352,19 +355,19 @@ void main() { ng.createMapUpdateWithConditionBindingTestFactory()); var testFixture = await testBed.create(); var content = testFixture.rootElement.querySelector('div')!; - expect(content.classes, equals(['init', 'foo', 'baz'])); + expect(content.classList, hasDomTokenList(['init', 'foo', 'baz'])); await testFixture.update((MapUpdateWithConditionBindingTest component) { component.map!['bar'] = true; }); - expect(content.classes, equals(['init', 'foo', 'baz', 'bar'])); + expect(content.classList, hasDomTokenList(['init', 'foo', 'baz', 'bar'])); await testFixture.update((MapUpdateWithConditionBindingTest component) { component.map!['foo'] = false; }); - expect(content.classes, equals(['init', 'baz', 'bar'])); + expect(content.classList, hasDomTokenList(['init', 'baz', 'bar'])); await testFixture.update((MapUpdateWithConditionBindingTest component) { component.condition = false; }); - expect(content.classes, equals(['init', 'bar'])); + expect(content.classList, hasDomTokenList(['init', 'bar'])); }); test( @@ -374,19 +377,19 @@ void main() { ng.createMapUpdateWithStringBindingTestFactory()); var testFixture = await testBed.create(); var content = testFixture.rootElement.querySelector('div')!; - expect(content.classes, equals(['init', 'foo'])); + expect(content.classList, hasDomTokenList(['init', 'foo'])); await testFixture.update((MapUpdateWithStringBindingTest component) { component.map!['bar'] = true; }); - expect(content.classes, equals(['init', 'foo', 'bar'])); + expect(content.classList, hasDomTokenList(['init', 'foo', 'bar'])); await testFixture.update((MapUpdateWithStringBindingTest component) { component.string = 'baz'; }); - expect(content.classes, equals(['init', 'bar', 'baz', 'foo'])); + expect(content.classList, hasDomTokenList(['init', 'bar', 'baz', 'foo'])); await testFixture.update((MapUpdateWithStringBindingTest component) { component.map = null; }); - expect(content.classes, equals(['init', 'baz'])); + expect(content.classList, hasDomTokenList(['init', 'baz'])); }); test( @@ -396,17 +399,17 @@ void main() { ng.createInterpolationWithConditionBindingTestFactory()); var testFixture = await testBed.create(); var content = testFixture.rootElement.querySelector('div')!; - expect(content.classes, equals(['foo', 'baz'])); + expect(content.classList, hasDomTokenList(['foo', 'baz'])); await testFixture .update((InterpolationWithConditionBindingTest component) { component.condition = false; }); - expect(content.classes, equals(['foo'])); + expect(content.classList, hasDomTokenList(['foo'])); await testFixture .update((InterpolationWithConditionBindingTest component) { component.condition = true; }); - expect(content.classes, equals(['foo', 'baz'])); + expect(content.classList, hasDomTokenList(['foo', 'baz'])); }); }); @@ -681,8 +684,16 @@ class MapUpdateWithStringBindingTest extends Base {} class InterpolationWithConditionBindingTest extends Base {} extension _SumCssClasses on Element { - Iterable get allCssClasses { - return querySelectorAll('*').map((e) => e.classes).expand((c) => c); + Iterable get allCssClasses sync* { + final nodes = querySelectorAll('*'); + final nodesLength = nodes.length; + for (var i = 0; i < nodesLength; i++) { + final classList = (nodes.item(i) as Element).classList; + final classListLength = classList.length; + for (var j = 0; j < classListLength; j++) { + yield classList.item(i)!; + } + } } } diff --git a/_tests/test/common/directives/ng_style_test.dart b/_tests/test/common/directives/ng_style_test.dart index 756dff719b..66995c8baf 100644 --- a/_tests/test/common/directives/ng_style_test.dart +++ b/_tests/test/common/directives/ng_style_test.dart @@ -1,6 +1,7 @@ import 'package:ngdart/angular.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'ng_style_test.template.dart' as ng; @@ -11,7 +12,7 @@ void main() { test('should update styles specified in an map literal', () async { var testBed = NgTestBed(ng.createMapUpdateTestFactory()); var testFixture = await testBed.create(); - var content = testFixture.rootElement.querySelector('div')!; + var content = testFixture.rootElement.querySelector('div') as HTMLElement; await testFixture.update((MapUpdateTest component) { component.map = {'max-width': '40px'}; }); @@ -25,7 +26,7 @@ void main() { test('should remove styles when deleting a key in a map literal', () async { var testBed = NgTestBed(ng.createMapUpdateTestFactory()); var testFixture = await testBed.create(); - var content = testFixture.rootElement.querySelector('div')!; + var content = testFixture.rootElement.querySelector('div') as HTMLElement; await testFixture.update((MapUpdateTest component) { component.map = {'max-width': '40px'}; }); @@ -40,7 +41,7 @@ void main() { var testBed = NgTestBed( ng.createMapUpdateWithDefaultTestFactory()); var testFixture = await testBed.create(); - var content = testFixture.rootElement.querySelector('div')!; + var content = testFixture.rootElement.querySelector('div') as HTMLElement; await testFixture.update((MapUpdateWithDefaultTest component) { component.map = {'max-width': '40px'}; }); @@ -58,7 +59,7 @@ void main() { var testBed = NgTestBed( ng.createMapUpdateWithStyleExprTestFactory()); var testFixture = await testBed.create(); - var content = testFixture.rootElement.querySelector('div')!; + var content = testFixture.rootElement.querySelector('div') as HTMLElement; await testFixture.update((MapUpdateWithStyleExprTest component) { component.map = {'max-width': '40px'}; }); diff --git a/_tests/test/common/pipes/async_pipe_test.dart b/_tests/test/common/pipes/async_pipe_test.dart index e7b31e9d7e..72722c7729 100644 --- a/_tests/test/common/pipes/async_pipe_test.dart +++ b/_tests/test/common/pipes/async_pipe_test.dart @@ -175,5 +175,5 @@ class FakeChangeDetectorRef implements ChangeDetectorRef { } @override - dynamic noSuchMethod(_) => super.noSuchMethod(_); + dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } diff --git a/_tests/test/compiler/expression_parser/analyzer_test.dart b/_tests/test/compiler/expression_parser/analyzer_test.dart index 0a26291b16..0e8c338936 100644 --- a/_tests/test/compiler/expression_parser/analyzer_test.dart +++ b/_tests/test/compiler/expression_parser/analyzer_test.dart @@ -6,7 +6,7 @@ import 'package:test/test.dart'; import 'unparser.dart'; const _isParseException = TypeMatcher(); -const _throwsParseException = Throws(_isParseException); +final _throwsParseException = throwsA(_isParseException); void main() { final parser = AnalyzerExpressionParser(); diff --git a/_tests/test/compiler_integration/invalid_late_fields_test.dart b/_tests/test/compiler_integration/invalid_late_fields_test.dart index 01ea076e08..064c534e32 100644 --- a/_tests/test/compiler_integration/invalid_late_fields_test.dart +++ b/_tests/test/compiler_integration/invalid_late_fields_test.dart @@ -24,9 +24,10 @@ void main() { test('should refuse to compile non-nullable single child query', () async { await compilesExpecting(""" - import 'dart:html'; import '$ngImport'; + import 'package:web/web.dart'; + @Component( selector: 'example-comp', template: '
', @@ -42,9 +43,10 @@ void main() { test('should refuse to compile late fields with a child query', () async { await compilesExpecting(""" - import 'dart:html'; import '$ngImport'; + import 'package:web/web.dart'; + @Component( selector: 'example-comp', template: '
', @@ -60,9 +62,10 @@ void main() { test('should refuse to compile late fields with a children query', () async { await compilesExpecting(""" - import 'dart:html'; import '$ngImport'; + import 'package:web/web.dart'; + @Component( selector: 'example-comp', template: '
', @@ -78,9 +81,10 @@ void main() { test('should compile non-nullable fields with a children query', () async { await compilesNormally(""" - import 'dart:html'; import '$ngImport'; + import 'package:web/web.dart'; + @Component( selector: 'example-comp', template: '
', diff --git a/_tests/test/core/application_ref_test.dart b/_tests/test/core/application_ref_test.dart index 13a68cd6b8..873cc5907b 100644 --- a/_tests/test/core/application_ref_test.dart +++ b/_tests/test/core/application_ref_test.dart @@ -1,11 +1,11 @@ import 'dart:async'; -import 'dart:html'; import 'package:ngdart/angular.dart'; import 'package:ngdart/src/core/application_ref.dart'; import 'package:ngdart/src/core/linker/app_view_utils.dart'; import 'package:ngdart/src/runtime/dom_events.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'application_ref_test.template.dart' as ng; @@ -55,11 +55,12 @@ void main() { group('bootstrap should', () { test('replace an existing element if in the DOM', () { - final existing = Element.tag('hello-component')..text = 'Loading...'; + final existing = document.createElement('hello-component') + ..textContent = 'Loading...'; document.body!.append(existing); final comp = appRef.bootstrap(ng.createHelloComponentFactory()); - expect(comp.location.text, 'Hello World'); + expect(comp.location.textContent, 'Hello World'); expect( document.body!.querySelector('hello-component'), same(comp.location), @@ -69,7 +70,7 @@ void main() { test('create a new element if missing from the DOM', () { final comp = appRef.bootstrap(ng.createHelloComponentFactory()); - expect(comp.location.text, 'Hello World'); + expect(comp.location.textContent, 'Hello World'); expect( document.body!.querySelector('hello-component'), same(comp.location), @@ -100,12 +101,12 @@ void main() { test('return an asynchronous null', () { final result = appRef.run(() async => null); - expect(result, isInstanceOf>()); + expect(result, isA>()); }); test('return an asynchronous nullable value', () { final result = appRef.run(() async => null); - expect(result, isInstanceOf>()); + expect(result, isA>()); }); test('never return (threw synchronously)', () { diff --git a/_tests/test/core/change_detection/detect_host_changes_test.dart b/_tests/test/core/change_detection/detect_host_changes_test.dart index 6d349e1c1a..b6459eb12c 100644 --- a/_tests/test/core/change_detection/detect_host_changes_test.dart +++ b/_tests/test/core/change_detection/detect_host_changes_test.dart @@ -1,8 +1,7 @@ -import 'dart:html'; - import 'package:ngdart/angular.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'detect_host_changes_test.template.dart' as ng; @@ -19,8 +18,8 @@ void main() { var testBed = NgTestBed(ng.createTestContainerFactory()); var testRoot = await testBed.create(); var targetElement = testRoot.rootElement.querySelector('.mytarget')!; - expect(targetElement.firstChild!.text, 'ChildHello'); - expect(targetElement.attributes['data-xyz'], 'abc'); + expect(targetElement.firstChild!.textContent, 'ChildHello'); + expect(targetElement.attributes.getNamedItem('data-xyz'), 'abc'); }); } @@ -58,5 +57,5 @@ class SomeDirective { void handleClick(Event e) {} @HostListener('keypress') - void handleKeyPress(KeyEvent e) {} + void handleKeyPress(KeyboardEvent e) {} } diff --git a/_tests/test/core/change_detection/differs/default_iterable_differ_test.dart b/_tests/test/core/change_detection/differs/default_iterable_differ_test.dart index abd3b213c6..e2427607bd 100644 --- a/_tests/test/core/change_detection/differs/default_iterable_differ_test.dart +++ b/_tests/test/core/change_detection/differs/default_iterable_differ_test.dart @@ -1,5 +1,3 @@ -library angular2.test.core.change_detection.differs.default_iterable_differ_test; - import 'dart:collection'; import 'package:ngdart/src/core/change_detection/differs/default_iterable_differ.dart'; diff --git a/_tests/test/core/change_detection/on_push_embedded_view_test.dart b/_tests/test/core/change_detection/on_push_embedded_view_test.dart index 48ec96ad90..189b6bf182 100644 --- a/_tests/test/core/change_detection/on_push_embedded_view_test.dart +++ b/_tests/test/core/change_detection/on_push_embedded_view_test.dart @@ -1,8 +1,7 @@ -import 'dart:html'; - import 'package:ngdart/angular.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'on_push_embedded_view_test.template.dart' as ng; @@ -39,25 +38,27 @@ void main() { test('when embedded within the OnPush component of origin', () async { final component = fixture.assertOnlyInstance; - expect(component.templateProducer!.text, isEmpty); + expect(component.templateProducer!.textContent, isEmpty); await fixture.update((component) { component.templateText = 'Hello template!'; }); - expect(component.templateProducer!.text, contains('Hello template!')); + expect( + component.templateProducer!.textContent, contains('Hello template!')); await fixture.update((component) { component.templateText = 'Goodbye template!'; }); - expect(component.templateProducer!.text, contains('Goodbye template!')); + expect(component.templateProducer!.textContent, + contains('Goodbye template!')); }); test('when embedded within a separate OnPush component', () async { final component = fixture.assertOnlyInstance; - expect(component.templateConsumer!.text, isEmpty); + expect(component.templateConsumer!.textContent, isEmpty); await fixture.update((component) { component.templateText = 'Hello template!'; @@ -67,7 +68,7 @@ void main() { // views to be updated. Any templates that originated within its view that // are embedded in a foreign OnPush view don't receive these changes. expect( - component.templateConsumer!.text, + component.templateConsumer!.textContent, contains('Hello template!'), skip: 'b/130433627', ); @@ -79,13 +80,14 @@ void main() { // The above change that was previously expected is now observed in the // template embedded in a foreign view container with an OnPush parent. expect( - component.templateConsumer!.text, + component.templateConsumer!.textContent, contains('Hello template!'), reason: 'Unrelated change to view container parent triggers change ' 'detection of nested views which delivers an old change from the ' 'template parent to the embedded view.', ); - expect(component.templateConsumer!.text, contains('Hello consumer!')); + expect( + component.templateConsumer!.textContent, contains('Hello consumer!')); }); }); } diff --git a/_tests/test/core/directive_inheritance_test.dart b/_tests/test/core/directive_inheritance_test.dart index 696e9c61a6..05a637153e 100644 --- a/_tests/test/core/directive_inheritance_test.dart +++ b/_tests/test/core/directive_inheritance_test.dart @@ -1,9 +1,9 @@ import 'dart:async'; -import 'dart:html'; import 'package:ngdart/angular.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'directive_inheritance_test.template.dart' as ng; diff --git a/_tests/test/core/directive_lifecycle_integration_test.dart b/_tests/test/core/directive_lifecycle_integration_test.dart index 2713940385..ff7b750ada 100644 --- a/_tests/test/core/directive_lifecycle_integration_test.dart +++ b/_tests/test/core/directive_lifecycle_integration_test.dart @@ -1,5 +1,3 @@ -library angular2.test.core.directive_lifecycle_integration_test; - import 'package:ngdart/angular.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; diff --git a/_tests/test/core/event_handler_test.dart b/_tests/test/core/event_handler_test.dart index d942fd34b9..6274001ea2 100644 --- a/_tests/test/core/event_handler_test.dart +++ b/_tests/test/core/event_handler_test.dart @@ -1,9 +1,9 @@ import 'dart:async'; -import 'dart:html'; import 'package:ngdart/angular.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'event_handler_test.template.dart' as ng; @@ -55,7 +55,8 @@ void main() { ); final fixture = await testBed.create(); await fixture.update((_) { - fixture.rootElement.querySelector('button')!.click(); + (fixture.rootElement.querySelector('button') as HTMLButtonElement) + .click(); }); expect(fixture.assertOnlyInstance.captured, ['bar']); }); @@ -66,7 +67,8 @@ void main() { ); final fixture = await testBed.create(); await fixture.update((_) { - fixture.rootElement.querySelector('button')!.click(); + (fixture.rootElement.querySelector('button') as HTMLButtonElement) + .click(); }); expect(fixture.assertOnlyInstance.captured, ['bar']); }); @@ -77,7 +79,8 @@ void main() { ); final fixture = await testBed.create(); await fixture.update((_) { - fixture.rootElement.querySelector('button')!.click(); + (fixture.rootElement.querySelector('button') as HTMLButtonElement) + .click(); }); expect(fixture.assertOnlyInstance.captured, ['bar']); }); @@ -88,7 +91,8 @@ void main() { ); final fixture = await testBed.create(); await fixture.update((_) { - fixture.rootElement.querySelector('button')!.click(); + (fixture.rootElement.querySelector('button') as HTMLButtonElement) + .click(); }); expect(fixture.assertOnlyInstance.captured, ['bar']); }); @@ -99,7 +103,8 @@ void main() { ); final fixture = await testBed.create(); await fixture.update((_) { - fixture.rootElement.querySelector('button')!.click(); + (fixture.rootElement.querySelector('button') as HTMLButtonElement) + .click(); }); expect(fixture.assertOnlyInstance.captured, ['bar']); }); @@ -110,7 +115,8 @@ void main() { ); final fixture = await testBed.create(); await fixture.update((_) { - fixture.rootElement.querySelector('button')!.click(); + (fixture.rootElement.querySelector('button') as HTMLButtonElement) + .click(); }); expect(fixture.assertOnlyInstance.captured, ['bar']); }); @@ -123,7 +129,8 @@ void main() { final fixture = await testBed.create(); overrideTopLevelDoCapture = expectAsync0(() {}); await fixture.update((_) { - fixture.rootElement.querySelector('button')!.click(); + (fixture.rootElement.querySelector('button') as HTMLButtonElement) + .click(); }); }, skip: 'https://github.com/angulardart/angular/issues/1670'); @@ -134,7 +141,8 @@ void main() { final fixture = await testBed.create(); overrideTopLevelDoCapture = expectAsync0(() {}); await fixture.update((_) { - fixture.rootElement.querySelector('button')!.click(); + (fixture.rootElement.querySelector('button') as HTMLButtonElement) + .click(); }); }); @@ -145,7 +153,8 @@ void main() { final fixture = await testBed.create(); TestStaticMethods.overrideDoCapture = expectAsync0(() {}); await fixture.update((_) { - fixture.rootElement.querySelector('button')!.click(); + (fixture.rootElement.querySelector('button') as HTMLButtonElement) + .click(); }); }); @@ -156,7 +165,8 @@ void main() { final fixture = await testBed.create(); TestStaticMethodsDirect.overrideDoCapture = expectAsync0(() {}); await fixture.update((_) { - fixture.rootElement.querySelector('button')!.click(); + (fixture.rootElement.querySelector('button') as HTMLButtonElement) + .click(); }); }); @@ -167,7 +177,8 @@ void main() { final fixture = await testBed.create(); fixture.assertOnlyInstance.bar.overrideDoCapture = expectAsync0(() {}); await fixture.update((_) { - fixture.rootElement.querySelector('button')!.click(); + (fixture.rootElement.querySelector('button') as HTMLButtonElement) + .click(); }); }, skip: 'https://github.com/angulardart/angular/issues/1670'); @@ -178,7 +189,8 @@ void main() { final fixture = await testBed.create(); fixture.assertOnlyInstance.bar.overrideDoCapture = expectAsync0(() {}); await fixture.update((_) { - fixture.rootElement.querySelector('button')!.click(); + (fixture.rootElement.querySelector('button') as HTMLButtonElement) + .click(); }); }); @@ -188,7 +200,7 @@ void main() { ng.createComponentWithHostEventThatThrowsFactory()); final fixture = await testBed.create(); expect( - fixture.update((_) => fixture.rootElement.click()), + fixture.update((_) => (fixture.rootElement as HTMLElement).click()), throwsIntentional, ); }); @@ -206,19 +218,19 @@ void main() { ) class ClickHandler extends SuperClick { @ViewChild('noArg') - HtmlElement? noArgButton; + HTMLElement? noArgButton; @ViewChild('oneArg') - HtmlElement? oneArgButton; + HTMLElement? oneArgButton; @ViewChild('noArgTearoff') - HtmlElement? noArgTearoffButton; + HTMLElement? noArgTearoffButton; @ViewChild('oneArgTearoff') - HtmlElement? oneArgTearoffButton; + HTMLElement? oneArgTearoffButton; @ViewChild('superTearoff') - HtmlElement? superTearoffButton; + HTMLElement? superTearoffButton; void onClick() { _clicks.add(null); diff --git a/_tests/test/core/exports_test.dart b/_tests/test/core/exports_test.dart index 837a6df42d..5abedcefee 100644 --- a/_tests/test/core/exports_test.dart +++ b/_tests/test/core/exports_test.dart @@ -1,6 +1,7 @@ import 'package:ngdart/angular.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'exports_statics.dart' as lib; import 'exports_statics.dart'; @@ -54,7 +55,7 @@ void main() { var testBed = NgTestBed( ng.createStaticEventHandlerTestFactory()); var fixture = await testBed.create(); - var div = fixture.rootElement.querySelector('div')!; + var div = fixture.rootElement.querySelector('div') as HTMLDivElement; clickHandled = false; await fixture.update((_) { div.click(); @@ -66,7 +67,7 @@ void main() { var testBed = NgTestBed( ng.createStaticEventHandlerTargetTestFactory()); var fixture = await testBed.create(); - var div = fixture.rootElement.querySelector('div')!; + var div = fixture.rootElement.querySelector('div') as HTMLDivElement; MyClass.clickHandled = false; await fixture.update((_) { div.click(); @@ -78,7 +79,7 @@ void main() { var testBed = NgTestBed( ng.createStaticEventHandlerArgTestFactory()); var fixture = await testBed.create(); - var div = fixture.rootElement.querySelector('div')!; + var div = fixture.rootElement.querySelector('div') as HTMLDivElement; late List listArg; await fixture.update((StaticEventHandlerArgTest component) { component.clickHandler = (list) { @@ -103,11 +104,11 @@ void main() { var testBed = NgTestBed( ng.createSelfReferHostBindingTestFactory()); var fixture = await testBed.create(); - expect(fixture.rootElement.title, 'hello'); + expect((fixture.rootElement as HTMLElement).title, 'hello'); await fixture.update((_) { SelfReferHostBindingTest.staticField = 'goodbye'; }); - expect(fixture.rootElement.title, 'goodbye'); + expect((fixture.rootElement as HTMLElement).title, 'goodbye'); }); group('can be prefixed', () { diff --git a/_tests/test/core/host_annotation_test.dart b/_tests/test/core/host_annotation_test.dart index 92b9142996..23b9cacb8f 100644 --- a/_tests/test/core/host_annotation_test.dart +++ b/_tests/test/core/host_annotation_test.dart @@ -1,8 +1,7 @@ -import 'dart:html'; - import 'package:ngdart/angular.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'host_annotation_test.template.dart' as ng; @@ -10,11 +9,13 @@ void main() { tearDown(disposeAnyRunningTest); /// Returns the root [Element] created by initializing [component]. - Future rootElementOf( + Future rootElementOf( ComponentFactory component, ) { final testBed = NgTestBed(component); - return testBed.create().then((fixture) => fixture.rootElement); + return testBed + .create() + .then((fixture) => fixture.rootElement as HTMLElement); } group('@HostBinding', () { @@ -116,13 +117,13 @@ void main() { ); final fixture = await testBed.create(); final element = fixture.rootElement; - expect(element.classes, isNot(contains('fancy'))); + expect(element.classList.value, isNot(contains('fancy'))); await fixture.update((c) => c.fancy = true); - expect(element.classes, contains('fancy')); + expect(element.classList.value, contains('fancy')); await fixture.update((c) => c.fancy = false); - expect(element.classes, isNot(contains('fancy'))); + expect(element.classList.value, isNot(contains('fancy'))); }); test('should support multiple annotations on a single field', () async { @@ -139,7 +140,7 @@ void main() { NgTestBed(ng.createHostListenerClickFactory()); final fixture = await testBed.create(); fixture.assertOnlyInstance.clickHandler = expectAsync0(() {}); - await fixture.update((_) => fixture.rootElement.click()); + await fixture.update((_) => (fixture.rootElement as HTMLElement).click()); }); test('should support click through inheritance', () async { @@ -147,7 +148,7 @@ void main() { ng.createHostListenerInheritedClickFactory()); final fixture = await testBed.create(); fixture.assertOnlyInstance.clickHandler = expectAsync0(() {}); - await fixture.update((_) => fixture.rootElement.click()); + await fixture.update((_) => (fixture.rootElement as HTMLElement).click()); }); test('should support multiple annotations on a single field', () async { diff --git a/_tests/test/core/i18n_escape_test.dart b/_tests/test/core/i18n_escape_test.dart index 3896dfd2a2..14f82d25a9 100644 --- a/_tests/test/core/i18n_escape_test.dart +++ b/_tests/test/core/i18n_escape_test.dart @@ -1,8 +1,7 @@ -import 'dart:html'; - import 'package:ngdart/angular.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'i18n_escape_test.template.dart' as ng; @@ -40,7 +39,7 @@ void main() { ng.createShouldEscapeI18nPropertyFactory()); final testFixture = await testBed.create(); final imgElement = - testFixture.rootElement.querySelector('img') as ImageElement; + testFixture.rootElement.querySelector('img') as HTMLImageElement; expect(imgElement.alt, matches(regExp)); }); diff --git a/_tests/test/core/i18n_test.dart b/_tests/test/core/i18n_test.dart index e3f54808c8..50f77b2ce9 100644 --- a/_tests/test/core/i18n_test.dart +++ b/_tests/test/core/i18n_test.dart @@ -1,8 +1,7 @@ -import 'dart:html'; - import 'package:ngdart/angular.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'i18n_test.template.dart' as ng; @@ -20,7 +19,7 @@ void main() { NgTestBed(ng.createTestI18nAttributeFactory()); final testFixture = await testBed.create(); final imgElement = - testFixture.rootElement.querySelector('img') as ImageElement; + testFixture.rootElement.querySelector('img') as HTMLImageElement; expect(imgElement.alt, 'A puppy!'); }); @@ -32,7 +31,7 @@ void main() { final lineBreaks = testFixture.rootElement.querySelectorAll('br'); expect(lineBreaks, hasLength(1)); final strongElement = testFixture.rootElement.querySelector('strong')!; - expect(strongElement.text, 'emphasis!'); + expect(strongElement.textContent, 'emphasis!'); }); test('should render message with unsafe HTML', () async { @@ -57,7 +56,7 @@ void main() { final testFixture = await testBed.create(); expect(testFixture.text, 'Italic, not italic.'); final italicElement = testFixture.rootElement.querySelector('i')!; - expect(italicElement.text, 'Italic'); + expect(italicElement.textContent, 'Italic'); }); // This test ensures none of our Intl.message() parameters are invalid. diff --git a/_tests/test/core/linker/component_loader_test.dart b/_tests/test/core/linker/component_loader_test.dart index 44de40109d..915daf0957 100644 --- a/_tests/test/core/linker/component_loader_test.dart +++ b/_tests/test/core/linker/component_loader_test.dart @@ -48,7 +48,7 @@ void main() { ng.createDynamicCompFactory(), injector: logInjector(comp.context), ); - expect(ref.location.text, 'Dynamic'); + expect(ref.location.textContent, 'Dynamic'); }); }); @@ -149,7 +149,7 @@ void main() { ng.createDynamicOnPushCompFactory(), injector: logInjector(comp.context), ); - expect(ref.location.text, 'Dynamic'); + expect(ref.location.textContent, 'Dynamic'); }); }); diff --git a/_tests/test/core/linker/component_selector_test.dart b/_tests/test/core/linker/component_selector_test.dart index 766490f6fe..160142632e 100644 --- a/_tests/test/core/linker/component_selector_test.dart +++ b/_tests/test/core/linker/component_selector_test.dart @@ -21,72 +21,93 @@ void main() { final testBed = NgTestBed( ng.createExactAttributeSelectorTestComponentFactory()); final testFixture = await testBed.create(); - final select = testFixture.rootElement.querySelector; - expect(select('[foo]')!.text, isEmpty); - expect(select('[foo=bar]')!.text, 'Matched!'); - expect(select('[foo=barbaz]')!.text, isEmpty); + expect( + testFixture.rootElement.querySelector('[foo]')!.textContent, isEmpty); + expect(testFixture.rootElement.querySelector('[foo=bar]')!.textContent, + 'Matched!'); + expect(testFixture.rootElement.querySelector('[foo=barbaz]')!.textContent, + isEmpty); }); test('should support hypen attribute selector', () async { final testBed = NgTestBed( ng.createHyphenAttributeSelectorTestComponentFactory()); final testFixture = await testBed.create(); - final select = testFixture.rootElement.querySelector; - expect(select('[foo=bar]')!.text, 'Matched!'); - expect(select('[foo="bar-baz"]')!.text, 'Matched!'); - expect(select('[foo=barbaz]')!.text, isEmpty); + expect(testFixture.rootElement.querySelector('[foo=bar]')!.textContent, + 'Matched!'); + expect( + testFixture.rootElement.querySelector('[foo="bar-baz"]')!.textContent, + 'Matched!'); + expect(testFixture.rootElement.querySelector('[foo=barbaz]')!.textContent, + isEmpty); }); test('should support list attribute selector', () async { final testBed = NgTestBed( ng.createListAttributeSelectorTestComponentFactory()); final testFixture = await testBed.create(); - final select = testFixture.rootElement.querySelector; - expect(select('[foo=bar]')!.text, 'Matched!'); - expect(select('[foo="bar baz"]')!.text, 'Matched!'); - expect(select('[foo="baz bar qux"]')!.text, 'Matched!'); - expect(select('[foo=barbaz]')!.text, isEmpty); + expect(testFixture.rootElement.querySelector('[foo=bar]')!.textContent, + 'Matched!'); + expect( + testFixture.rootElement.querySelector('[foo="bar baz"]')!.textContent, + 'Matched!'); + expect( + testFixture.rootElement + .querySelector('[foo="baz bar qux"]')! + .textContent, + 'Matched!'); + expect(testFixture.rootElement.querySelector('[foo=barbaz]')!.textContent, + isEmpty); }); test('should support prefix attribute selector', () async { final testBed = NgTestBed( ng.createPrefixAttributeSelectorTestComponentFactory()); final testFixture = await testBed.create(); - final select = testFixture.rootElement.querySelector; - expect(select('[foo=bar]')!.text, 'Matched!'); - expect(select('[foo=barbaz]')!.text, 'Matched!'); - expect(select('[foo=bazbar]')!.text, isEmpty); + expect(testFixture.rootElement.querySelector('[foo=bar]')!.textContent, + 'Matched!'); + expect(testFixture.rootElement.querySelector('[foo=barbaz]')!.textContent, + 'Matched!'); + expect(testFixture.rootElement.querySelector('[foo=bazbar]')!.textContent, + isEmpty); }); test('should support set attribute selector', () async { final testBed = NgTestBed( ng.createSetAttributeSelectorTestComponentFactory()); final testFixture = await testBed.create(); - final select = testFixture.rootElement.querySelector; - expect(select('div')!.text, isEmpty); - expect(select('[foo]')!.text, 'Matched!'); - expect(select('[foo=""]')!.text, 'Matched!'); - expect(select('[foo="bar"]')!.text, 'Matched!'); + expect( + testFixture.rootElement.querySelector('div')!.textContent, isEmpty); + expect(testFixture.rootElement.querySelector('[foo]')!.textContent, + 'Matched!'); + expect(testFixture.rootElement.querySelector('[foo=""]')!.textContent, + 'Matched!'); + expect(testFixture.rootElement.querySelector('[foo="bar"]')!.textContent, + 'Matched!'); }); test('should support substring attribute selector', () async { final testBed = NgTestBed( ng.createSubstringAttributeSelectorTestComponentFactory()); final testFixture = await testBed.create(); - final select = testFixture.rootElement.querySelector; - expect(select('[foo=bar]')!.text, 'Matched!'); - expect(select('[foo=barbaz]')!.text, 'Matched!'); - expect(select('[foo=bazbar]')!.text, 'Matched!'); + expect(testFixture.rootElement.querySelector('[foo=bar]')!.textContent, + 'Matched!'); + expect(testFixture.rootElement.querySelector('[foo=barbaz]')!.textContent, + 'Matched!'); + expect(testFixture.rootElement.querySelector('[foo=bazbar]')!.textContent, + 'Matched!'); }); test('should support suffix attribute selector', () async { final testBed = NgTestBed( ng.createSuffixAttributeSelectorTestComponentFactory()); final testFixture = await testBed.create(); - final select = testFixture.rootElement.querySelector; - expect(select('[foo=bar]')!.text, 'Matched!'); - expect(select('[foo=barbaz]')!.text, isEmpty); - expect(select('[foo=bazbar]')!.text, 'Matched!'); + expect(testFixture.rootElement.querySelector('[foo=bar]')!.textContent, + 'Matched!'); + expect(testFixture.rootElement.querySelector('[foo=barbaz]')!.textContent, + isEmpty); + expect(testFixture.rootElement.querySelector('[foo=bazbar]')!.textContent, + 'Matched!'); }); }); } diff --git a/_tests/test/core/linker/implicit_static_test.dart b/_tests/test/core/linker/implicit_static_test.dart index 2c08723484..0f0f06a00a 100644 --- a/_tests/test/core/linker/implicit_static_test.dart +++ b/_tests/test/core/linker/implicit_static_test.dart @@ -1,8 +1,7 @@ -import 'dart:html'; - import 'package:ngdart/angular.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'implicit_static_test.template.dart' as ng; @@ -114,7 +113,7 @@ class InvokeTearOff { @Input() set invoke(String Function() value) { - _host.text = value(); + _host.textContent = value(); } } diff --git a/_tests/test/core/linker/integration/binding_integration_test.dart b/_tests/test/core/linker/integration/binding_integration_test.dart index a882af18c4..eae3e8f1c2 100644 --- a/_tests/test/core/linker/integration/binding_integration_test.dart +++ b/_tests/test/core/linker/integration/binding_integration_test.dart @@ -1,6 +1,9 @@ +import 'dart:js_interop'; + import 'package:ngdart/angular.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'binding_integration_test.template.dart' as ng; @@ -59,7 +62,7 @@ void main() { final testBed = NgTestBed(ng.createBoundStyleComponentFactory()); final testFixture = await testBed.create(); - final div = testFixture.rootElement.querySelector('div')!; + final div = testFixture.rootElement.querySelector('div') as HTMLDivElement; expect(div.style.height, '10px'); await testFixture.update((component) => component.height = null); expect(div.style.height, ''); @@ -69,7 +72,7 @@ void main() { final testBed = NgTestBed( ng.createBoundMismatchedPropertyComponentFactory()); final testFixture = await testBed.create(); - final div = testFixture.rootElement.querySelector('div')!; + final div = testFixture.rootElement.querySelector('div') as HTMLDivElement; expect(div.tabIndex, 0); await testFixture.update((component) => component.index = 5); expect(div.tabIndex, 5); @@ -79,7 +82,7 @@ void main() { final testBed = NgTestBed( ng.createBoundCamelCasePropertyComponentFactory()); final testFixture = await testBed.create(); - final div = testFixture.rootElement.querySelector('div')!; + final div = testFixture.rootElement.querySelector('div') as HTMLDivElement; expect(div.tabIndex, 1); await testFixture.update((component) => component.index = 0); expect(div.tabIndex, 0); @@ -89,21 +92,21 @@ void main() { final testBed = NgTestBed( ng.createBoundInnerHtmlComponentFactory()); final testFixture = await testBed.create(); - final div = testFixture.rootElement.querySelector('div')!; - expect(div.innerHtml, 'Initial HTML'); + final div = testFixture.rootElement.querySelector('div') as HTMLDivElement; + expect((div.innerHTML as JSString).toDart, 'Initial HTML'); await testFixture .update((component) => component.html = 'New
HTML
'); - expect(div.innerHtml, 'New
HTML
'); + expect((div.innerHTML as JSString).toDart, 'New
HTML
'); }); test('should consume className binding using class alias', () async { final testBed = NgTestBed(ng.createBoundClassNameAliasFactory()); final testFixture = await testBed.create(); - final div = testFixture.rootElement.querySelector('div')!; - expect(div.classes, contains('foo')); - expect(div.classes, contains('bar')); - expect(div.classes, isNot(contains('initial'))); + final div = testFixture.rootElement.querySelector('div') as HTMLDivElement; + expect(div.classList.contains('foo'), isTrue); + expect(div.classList.contains('bar'), isTrue); + expect(div.classList.contains('initial'), isFalse); }); } diff --git a/_tests/test/core/linker/integration/directive_integration_test.dart b/_tests/test/core/linker/integration/directive_integration_test.dart index a34197f7f8..3b22f5264e 100644 --- a/_tests/test/core/linker/integration/directive_integration_test.dart +++ b/_tests/test/core/linker/integration/directive_integration_test.dart @@ -1,9 +1,9 @@ import 'dart:async'; -import 'dart:html'; import 'package:ngdart/angular.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'directive_integration_test.template.dart' as ng; @@ -203,8 +203,8 @@ class UnboundDirectiveInputComponent {} selector: '[no-duplicate]', ) class DuplicateDir { - DuplicateDir(HtmlElement element) { - element.text = '${element.text}noduplicate'; + DuplicateDir(HTMLElement element) { + element.textContent = '${element.textContent}noduplicate'; } } diff --git a/_tests/test/core/linker/integration/misc_test.dart b/_tests/test/core/linker/integration/misc_test.dart index 14eb02b89b..cabf41d007 100644 --- a/_tests/test/core/linker/integration/misc_test.dart +++ b/_tests/test/core/linker/integration/misc_test.dart @@ -1,6 +1,7 @@ import 'package:ngdart/angular.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; import 'misc_test.template.dart' as ng; @@ -18,7 +19,7 @@ void main() { final testBed = NgTestBed( ng.createHostAttributeFromDirectiveComponentFactory()); final testFixture = await testBed.create(); - final div = testFixture.rootElement.children.first; + final div = testFixture.rootElement.children.item(0) as HTMLDivElement; expect(div.attributes, containsPair('role', 'button')); }); @@ -26,7 +27,7 @@ void main() { final testBed = NgTestBed( ng.createHostPropertyFromDirectiveComponentFactory()); final testFixture = await testBed.create(); - final div = testFixture.rootElement.children.first; + final div = testFixture.rootElement.children.item(0) as HTMLDivElement; expect(div.id, 'one'); await testFixture.update((component) => component.directive!.id = 'two'); expect(div.id, 'two'); @@ -149,9 +150,8 @@ class DynamicViewport { DynamicViewport(ViewContainerRef vc) { final myService = MyService()..greeting = 'dynamic greet'; final injector = Injector.map({MyService: myService}, vc.injector); - final factoryFuture = Future.value( - ng.createChildCompUsingServiceFactory(), - ); + final factoryFuture = + Future.value(ng.createChildCompUsingServiceFactory()); done = factoryFuture.then((componentFactory) => vc.createComponent(componentFactory, 0, injector)); } diff --git a/_tests/test/core/linker/integration/ng_container_test.dart b/_tests/test/core/linker/integration/ng_container_test.dart index 403a836a16..5b9193aa3b 100644 --- a/_tests/test/core/linker/integration/ng_container_test.dart +++ b/_tests/test/core/linker/integration/ng_container_test.dart @@ -1,3 +1,4 @@ +import 'package:_tests/matchers.dart'; import 'package:ngdart/angular.dart'; import 'package:ngtest/angular_test.dart'; import 'package:test/test.dart'; @@ -18,25 +19,25 @@ void main() { final testBed = NgTestBed(ng.createRendersChildrenFactory()); final testFixture = await testBed.create(); - expect(testFixture.rootElement.innerHtml, html); + expect(testFixture.rootElement, hasInnerHtml(html)); }); test('supports *ngFor', () async { final testBed = NgTestBed(ng.createSupportsNgForFactory()); final testFixture = await testBed.create(); - expect(testFixture.rootElement.innerHtml, anchorHtml); + expect(testFixture.rootElement, hasInnerHtml(anchorHtml)); final values = ['a', 'b', 'c']; final html = values.join(); await testFixture.update((component) => component.values.addAll(values)); - expect(testFixture.rootElement.innerHtml, '$anchorHtml$html'); + expect(testFixture.rootElement, hasInnerHtml('$anchorHtml$html')); }); test('supports *ngIf', () async { final testBed = NgTestBed(ng.createSupportsNgIfFactory()); final testFixture = await testBed.create(); - expect(testFixture.rootElement.innerHtml, anchorHtml); + expect(testFixture.rootElement, hasInnerHtml(anchorHtml)); await testFixture.update((component) => component.visible = true); - expect(testFixture.rootElement.innerHtml, '$anchorHtml$html'); + expect(testFixture.rootElement, hasInnerHtml('$anchorHtml$html')); }); test('supports *ngTemplateOutlet', () async { @@ -44,54 +45,59 @@ void main() { ng.createSupportsNgTemplateOutletFactory()); final testFixture = await testBed.create(); expect( - testFixture.rootElement.innerHtml, - '$anchorHtml ' //