From 6e0dffe55a61777e1fe1eccc58eacb8ee6d13615 Mon Sep 17 00:00:00 2001 From: Istvan Soos Date: Mon, 11 Aug 2025 16:25:54 +0200 Subject: [PATCH 1/3] New like button markup with class selectors. --- app/lib/frontend/dom/material.dart | 4 +- app/lib/frontend/templates/detail_page.dart | 8 +- app/lib/frontend/templates/package.dart | 9 +- .../views/pkg/liked_package_list.dart | 49 +++++++++++ .../templates/views/shared/detail/header.dart | 53 +---------- .../golden/pkg_activity_log_page.html | 10 +-- app/test/frontend/golden/pkg_admin_page.html | 10 +-- .../frontend/golden/pkg_changelog_page.html | 10 +-- .../frontend/golden/pkg_example_page.html | 10 +-- .../frontend/golden/pkg_install_page.html | 10 +-- app/test/frontend/golden/pkg_score_page.html | 10 +-- .../pkg_score_page_with_downloads_chart.html | 10 +-- app/test/frontend/golden/pkg_show_page.html | 10 +-- .../golden/pkg_show_page_discontinued.html | 10 +-- .../golden/pkg_show_page_flutter_plugin.html | 10 +-- .../golden/pkg_show_page_publisher.html | 10 +-- .../golden/pkg_show_page_retracted.html | 10 +-- ..._page_retracted_non_retracted_version.html | 10 +-- .../golden/pkg_show_version_page.html | 10 +-- .../frontend/golden/pkg_versions_page.html | 10 +-- .../testdata/goldens/packages/oxygen.html | 10 +-- .../goldens/packages/oxygen/changelog.html | 10 +-- .../goldens/packages/oxygen/example.html | 10 +-- .../goldens/packages/oxygen/install.html | 10 +-- .../goldens/packages/oxygen/license.html | 10 +-- .../goldens/packages/oxygen/score.html | 10 +-- .../goldens/packages/oxygen/versions.html | 10 +-- .../packages/oxygen/versions/1.0.0.html | 10 +-- .../oxygen/versions/1.0.0/changelog.html | 10 +-- .../oxygen/versions/1.0.0/example.html | 10 +-- .../oxygen/versions/1.0.0/install.html | 10 +-- .../oxygen/versions/1.0.0/license.html | 10 +-- .../packages/oxygen/versions/1.0.0/score.html | 10 +-- .../packages/oxygen/versions/2.0.0.html | 10 +-- pkg/_pub_shared/lib/data/page_data.dart | 2 - pkg/_pub_shared/lib/data/page_data.g.dart | 2 - pkg/pub_integration/test/like_test.dart | 67 ++++++++++++++ pkg/web_app/lib/src/likes.dart | 87 ++++++++++--------- pkg/web_css/lib/src/_detail_page.scss | 50 +++++------ 39 files changed, 347 insertions(+), 274 deletions(-) create mode 100644 pkg/pub_integration/test/like_test.dart diff --git a/app/lib/frontend/dom/material.dart b/app/lib/frontend/dom/material.dart index 8262b85cfe..78ba46ff54 100644 --- a/app/lib/frontend/dom/material.dart +++ b/app/lib/frontend/dom/material.dart @@ -108,13 +108,14 @@ d.Node raisedButton({ /// Renders a two-state material icon button d.Node iconButton({ - required String id, + String? id, required bool isOn, Map? attributes, required d.Image onIcon, required d.Image offIcon, bool disabled = false, String? title, + List? classes, }) { return d.element( 'button', @@ -122,6 +123,7 @@ d.Node iconButton({ classes: [ 'mdc-icon-button', if (isOn) 'mdc-icon-button--on', + ...?classes, ], attributes: { ...?attributes, diff --git a/app/lib/frontend/templates/detail_page.dart b/app/lib/frontend/templates/detail_page.dart index c511aacf93..1002df1d02 100644 --- a/app/lib/frontend/templates/detail_page.dart +++ b/app/lib/frontend/templates/detail_page.dart @@ -10,16 +10,13 @@ import 'views/shared/detail/tabs.dart'; final wideHeaderDetailPageClassName = '-wide-header-detail-page'; /// Renders the detail page's header template. -/// -/// The like button in the header will not be displayed when [isLiked] is null. d.Node renderDetailHeader({ required d.Node titleNode, d.Image? image, - int? packageLikes, - bool? isLiked, bool isFlutterFavorite = false, d.Node? metadataNode, d.Node? tagsNode, + d.Node? likeNode, /// Set true for more whitespace in the header. bool isLoose = false, @@ -28,10 +25,9 @@ d.Node renderDetailHeader({ titleNode: titleNode, metadataNode: metadataNode, tagsNode: tagsNode, + likeNode: likeNode, image: image, isLoose: isLoose, - isLiked: isLiked == true, - likeCount: packageLikes, isFlutterFavorite: isFlutterFavorite, ); } diff --git a/app/lib/frontend/templates/package.dart b/app/lib/frontend/templates/package.dart index ccad207151..fb8ed28845 100644 --- a/app/lib/frontend/templates/package.dart +++ b/app/lib/frontend/templates/package.dart @@ -5,6 +5,7 @@ import 'package:_pub_shared/data/page_data.dart'; import 'package:_pub_shared/search/tags.dart'; import 'package:collection/collection.dart' show IterableExtension; +import 'package:pub_dev/frontend/templates/views/pkg/liked_package_list.dart'; import '../../package/models.dart'; import '../../package/overrides.dart' show devDependencyPackages; @@ -129,8 +130,11 @@ d.Node renderPkgHeader(PackagePageData data) { package: package.name!, version: data.version.version!, ), - packageLikes: package.likes, - isLiked: data.isLiked, + likeNode: renderLikeButtonAndLabel( + package: package.name!, + likeCount: package.likes, + isLiked: data.isLiked, + ), isFlutterFavorite: (package.assignedTags ?? []).contains(PackageTags.isFlutterFavorite), metadataNode: metadataNode, @@ -307,7 +311,6 @@ PageData pkgPageData( version: selectedVersion.version!, publisherId: package.publisherId, isDiscontinued: package.isDiscontinued, - likes: package.likes, isLatest: package.latestVersion == selectedVersion.version, ), sessionAware: editable, diff --git a/app/lib/frontend/templates/views/pkg/liked_package_list.dart b/app/lib/frontend/templates/views/pkg/liked_package_list.dart index 83c6cc8668..e3654f3ea8 100644 --- a/app/lib/frontend/templates/views/pkg/liked_package_list.dart +++ b/app/lib/frontend/templates/views/pkg/liked_package_list.dart @@ -2,6 +2,8 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'package:_pub_shared/format/number_format.dart'; + import '../../../../account/models.dart'; import '../../../../shared/urls.dart' as urls; @@ -64,3 +66,50 @@ d.Node likedPackageListNode(List likes) { ), ); } + +d.Node renderLikeButtonAndLabel( + {required String package, required int likeCount, required bool isLiked}) { + return d.div( + classes: ['like-button-and-label'], + children: [ + material.iconButton( + classes: ['like-button-and-label--button'], + isOn: isLiked, + onIcon: d.Image( + src: staticUrls.getAssetUrl('/static/img/like-active.svg'), + alt: 'liked status: active', + width: 18, + height: 18, + ), + offIcon: d.Image( + src: staticUrls.getAssetUrl('/static/img/like-inactive.svg'), + alt: 'liked status: inactive', + width: 18, + height: 18, + ), + title: isLiked ? 'Unlike this package' : 'Like this package', + attributes: { + 'data-ga-click-event': 'toggle-like', + 'aria-pressed': isLiked ? 'true' : 'false', + }, + ), + d.span( + classes: ['like-button-and-label--count-wrapper'], + child: d.span( + classes: ['like-button-and-label--count'], + text: _formatPackageLikes(likeCount), + attributes: { + 'data-package': package, + 'data-value': likeCount.toString(), + }, + ), + ), + ], + ); +} + +// keep in-sync with pkg/web_app/lib/src/likes.dart +String? _formatPackageLikes(int? likesCount) { + if (likesCount == null) return null; + return formatWithSuffix(likesCount); +} diff --git a/app/lib/frontend/templates/views/shared/detail/header.dart b/app/lib/frontend/templates/views/shared/detail/header.dart index 631e34ebd6..3d58913647 100644 --- a/app/lib/frontend/templates/views/shared/detail/header.dart +++ b/app/lib/frontend/templates/views/shared/detail/header.dart @@ -2,10 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import 'package:_pub_shared/format/number_format.dart'; - import '../../../../dom/dom.dart' as d; -import '../../../../dom/material.dart' as material; import '../../../../static_files.dart'; d.Node detailHeaderNode({ @@ -13,8 +10,7 @@ d.Node detailHeaderNode({ required d.Node? metadataNode, required d.Node? tagsNode, required d.Image? image, - required bool isLiked, - required int? likeCount, + required d.Node? likeNode, required bool isFlutterFavorite, /// Set true for more whitespace in the header. @@ -116,50 +112,13 @@ d.Node detailHeaderNode({ child: titleNode, ), d.div(classes: ['metadata'], child: metadataNode), - if (tagsNode != null || likeCount != null) + if (tagsNode != null || likeNode != null) d.div( classes: ['detail-tags-and-like'], children: [ if (tagsNode != null) d.div(classes: ['detail-tags'], child: tagsNode), - if (likeCount != null) - d.div( - classes: ['detail-like'], - children: [ - material.iconButton( - id: '-pub-like-icon-button', - isOn: isLiked, - onIcon: d.Image( - src: staticUrls - .getAssetUrl('/static/img/like-active.svg'), - alt: 'liked status: active', - width: 18, - height: 18, - ), - offIcon: d.Image( - src: staticUrls.getAssetUrl( - '/static/img/like-inactive.svg'), - alt: 'liked status: inactive', - width: 18, - height: 18, - ), - title: isLiked - ? 'Unlike this package' - : 'Like this package', - attributes: { - 'data-ga-click-event': 'toggle-like', - 'aria-pressed': isLiked ? 'true' : 'false', - }, - ), - d.span( - classes: ['likes-count'], - child: d.span( - id: 'likes-count', - text: _formatPackageLikes(likeCount), - ), - ), - ], - ), + if (likeNode != null) likeNode, ], ), ], @@ -170,9 +129,3 @@ d.Node detailHeaderNode({ ), ]); } - -// keep in-sync with pkg/web_app/lib/src/likes.dart -String? _formatPackageLikes(int? likesCount) { - if (likesCount == null) return null; - return formatWithSuffix(likesCount); -} diff --git a/app/test/frontend/golden/pkg_activity_log_page.html b/app/test/frontend/golden/pkg_activity_log_page.html index c91318bc02..5c4aaba17e 100644 --- a/app/test/frontend/golden/pkg_activity_log_page.html +++ b/app/test/frontend/golden/pkg_activity_log_page.html @@ -31,7 +31,7 @@ - + @@ -185,13 +185,13 @@

Windows - diff --git a/app/test/frontend/golden/pkg_admin_page.html b/app/test/frontend/golden/pkg_admin_page.html index 7cdfe8ac6b..9b213e120b 100644 --- a/app/test/frontend/golden/pkg_admin_page.html +++ b/app/test/frontend/golden/pkg_admin_page.html @@ -31,7 +31,7 @@ - + @@ -185,13 +185,13 @@

Windows - diff --git a/app/test/frontend/golden/pkg_changelog_page.html b/app/test/frontend/golden/pkg_changelog_page.html index 69cad5e8fa..5e56233798 100644 --- a/app/test/frontend/golden/pkg_changelog_page.html +++ b/app/test/frontend/golden/pkg_changelog_page.html @@ -32,7 +32,7 @@ - + @@ -160,13 +160,13 @@

Windows - diff --git a/app/test/frontend/golden/pkg_example_page.html b/app/test/frontend/golden/pkg_example_page.html index b3df481285..9fc4ee5a00 100644 --- a/app/test/frontend/golden/pkg_example_page.html +++ b/app/test/frontend/golden/pkg_example_page.html @@ -32,7 +32,7 @@ - + @@ -160,13 +160,13 @@

Windows - diff --git a/app/test/frontend/golden/pkg_install_page.html b/app/test/frontend/golden/pkg_install_page.html index c709265254..945f474e64 100644 --- a/app/test/frontend/golden/pkg_install_page.html +++ b/app/test/frontend/golden/pkg_install_page.html @@ -32,7 +32,7 @@ - + @@ -160,13 +160,13 @@

Windows - diff --git a/app/test/frontend/golden/pkg_score_page.html b/app/test/frontend/golden/pkg_score_page.html index 46e5c0f8f0..5c3542850b 100644 --- a/app/test/frontend/golden/pkg_score_page.html +++ b/app/test/frontend/golden/pkg_score_page.html @@ -32,7 +32,7 @@ - + @@ -160,13 +160,13 @@

Windows - diff --git a/app/test/frontend/golden/pkg_score_page_with_downloads_chart.html b/app/test/frontend/golden/pkg_score_page_with_downloads_chart.html index 46e5c0f8f0..5c3542850b 100644 --- a/app/test/frontend/golden/pkg_score_page_with_downloads_chart.html +++ b/app/test/frontend/golden/pkg_score_page_with_downloads_chart.html @@ -32,7 +32,7 @@ - + @@ -160,13 +160,13 @@

Windows - diff --git a/app/test/frontend/golden/pkg_show_page.html b/app/test/frontend/golden/pkg_show_page.html index ff7577f5af..85448736ca 100644 --- a/app/test/frontend/golden/pkg_show_page.html +++ b/app/test/frontend/golden/pkg_show_page.html @@ -32,7 +32,7 @@ - + @@ -151,13 +151,13 @@

Windows - diff --git a/app/test/frontend/golden/pkg_show_page_discontinued.html b/app/test/frontend/golden/pkg_show_page_discontinued.html index 34ad63852f..850fcd6706 100644 --- a/app/test/frontend/golden/pkg_show_page_discontinued.html +++ b/app/test/frontend/golden/pkg_show_page_discontinued.html @@ -32,7 +32,7 @@ - + @@ -159,13 +159,13 @@

Windows - diff --git a/app/test/frontend/golden/pkg_show_page_flutter_plugin.html b/app/test/frontend/golden/pkg_show_page_flutter_plugin.html index 4cc5f122d7..77d3e2f3a7 100644 --- a/app/test/frontend/golden/pkg_show_page_flutter_plugin.html +++ b/app/test/frontend/golden/pkg_show_page_flutter_plugin.html @@ -32,7 +32,7 @@ - + @@ -149,13 +149,13 @@

macOS - diff --git a/app/test/frontend/golden/pkg_show_page_publisher.html b/app/test/frontend/golden/pkg_show_page_publisher.html index e5c0a75c53..4e8b0a1d15 100644 --- a/app/test/frontend/golden/pkg_show_page_publisher.html +++ b/app/test/frontend/golden/pkg_show_page_publisher.html @@ -32,7 +32,7 @@ - + @@ -154,13 +154,13 @@

Windows - diff --git a/app/test/frontend/golden/pkg_show_page_retracted.html b/app/test/frontend/golden/pkg_show_page_retracted.html index aaef9b5752..f6c5780d6d 100644 --- a/app/test/frontend/golden/pkg_show_page_retracted.html +++ b/app/test/frontend/golden/pkg_show_page_retracted.html @@ -32,7 +32,7 @@ - + @@ -142,13 +142,13 @@

retracted
- diff --git a/app/test/frontend/golden/pkg_show_page_retracted_non_retracted_version.html b/app/test/frontend/golden/pkg_show_page_retracted_non_retracted_version.html index 404479b1f3..2d255474d8 100644 --- a/app/test/frontend/golden/pkg_show_page_retracted_non_retracted_version.html +++ b/app/test/frontend/golden/pkg_show_page_retracted_non_retracted_version.html @@ -32,7 +32,7 @@ - + @@ -150,13 +150,13 @@

Windows - diff --git a/app/test/frontend/golden/pkg_show_version_page.html b/app/test/frontend/golden/pkg_show_version_page.html index 4a35d9411a..1353a017e5 100644 --- a/app/test/frontend/golden/pkg_show_version_page.html +++ b/app/test/frontend/golden/pkg_show_version_page.html @@ -32,7 +32,7 @@ - + @@ -160,13 +160,13 @@

Windows - diff --git a/app/test/frontend/golden/pkg_versions_page.html b/app/test/frontend/golden/pkg_versions_page.html index 460ee53314..c1ff00757a 100644 --- a/app/test/frontend/golden/pkg_versions_page.html +++ b/app/test/frontend/golden/pkg_versions_page.html @@ -31,7 +31,7 @@ - + @@ -159,13 +159,13 @@

Windows - diff --git a/app/test/task/testdata/goldens/packages/oxygen.html b/app/test/task/testdata/goldens/packages/oxygen.html index 8a5cad3406..03a9451961 100644 --- a/app/test/task/testdata/goldens/packages/oxygen.html +++ b/app/test/task/testdata/goldens/packages/oxygen.html @@ -32,7 +32,7 @@ - + @@ -152,13 +152,13 @@

Windows - diff --git a/app/test/task/testdata/goldens/packages/oxygen/changelog.html b/app/test/task/testdata/goldens/packages/oxygen/changelog.html index 53c454f450..5a5243f2cb 100644 --- a/app/test/task/testdata/goldens/packages/oxygen/changelog.html +++ b/app/test/task/testdata/goldens/packages/oxygen/changelog.html @@ -32,7 +32,7 @@ - + @@ -152,13 +152,13 @@

Windows - diff --git a/app/test/task/testdata/goldens/packages/oxygen/example.html b/app/test/task/testdata/goldens/packages/oxygen/example.html index 1919dcbc5c..6b0de2cb1c 100644 --- a/app/test/task/testdata/goldens/packages/oxygen/example.html +++ b/app/test/task/testdata/goldens/packages/oxygen/example.html @@ -32,7 +32,7 @@ - + @@ -152,13 +152,13 @@

Windows - diff --git a/app/test/task/testdata/goldens/packages/oxygen/install.html b/app/test/task/testdata/goldens/packages/oxygen/install.html index ee7adb904a..508d0095a2 100644 --- a/app/test/task/testdata/goldens/packages/oxygen/install.html +++ b/app/test/task/testdata/goldens/packages/oxygen/install.html @@ -32,7 +32,7 @@ - + @@ -152,13 +152,13 @@

Windows - diff --git a/app/test/task/testdata/goldens/packages/oxygen/license.html b/app/test/task/testdata/goldens/packages/oxygen/license.html index 289bbc55c9..96d5804299 100644 --- a/app/test/task/testdata/goldens/packages/oxygen/license.html +++ b/app/test/task/testdata/goldens/packages/oxygen/license.html @@ -32,7 +32,7 @@ - + @@ -152,13 +152,13 @@

Windows - diff --git a/app/test/task/testdata/goldens/packages/oxygen/score.html b/app/test/task/testdata/goldens/packages/oxygen/score.html index 21996826a6..9626ccbada 100644 --- a/app/test/task/testdata/goldens/packages/oxygen/score.html +++ b/app/test/task/testdata/goldens/packages/oxygen/score.html @@ -32,7 +32,7 @@ - + @@ -152,13 +152,13 @@

Windows - diff --git a/app/test/task/testdata/goldens/packages/oxygen/versions.html b/app/test/task/testdata/goldens/packages/oxygen/versions.html index 5be69d7fbe..0256c245e7 100644 --- a/app/test/task/testdata/goldens/packages/oxygen/versions.html +++ b/app/test/task/testdata/goldens/packages/oxygen/versions.html @@ -31,7 +31,7 @@ - + @@ -151,13 +151,13 @@

Windows - diff --git a/app/test/task/testdata/goldens/packages/oxygen/versions/1.0.0.html b/app/test/task/testdata/goldens/packages/oxygen/versions/1.0.0.html index 5345aa5a88..c78d2d7123 100644 --- a/app/test/task/testdata/goldens/packages/oxygen/versions/1.0.0.html +++ b/app/test/task/testdata/goldens/packages/oxygen/versions/1.0.0.html @@ -32,7 +32,7 @@ - + @@ -156,13 +156,13 @@

Windows - diff --git a/app/test/task/testdata/goldens/packages/oxygen/versions/1.0.0/changelog.html b/app/test/task/testdata/goldens/packages/oxygen/versions/1.0.0/changelog.html index de33860e3f..717ca5a7e8 100644 --- a/app/test/task/testdata/goldens/packages/oxygen/versions/1.0.0/changelog.html +++ b/app/test/task/testdata/goldens/packages/oxygen/versions/1.0.0/changelog.html @@ -32,7 +32,7 @@ - + @@ -156,13 +156,13 @@

Windows - diff --git a/app/test/task/testdata/goldens/packages/oxygen/versions/1.0.0/example.html b/app/test/task/testdata/goldens/packages/oxygen/versions/1.0.0/example.html index 0015d0709d..ad55ee6250 100644 --- a/app/test/task/testdata/goldens/packages/oxygen/versions/1.0.0/example.html +++ b/app/test/task/testdata/goldens/packages/oxygen/versions/1.0.0/example.html @@ -32,7 +32,7 @@ - + @@ -156,13 +156,13 @@

Windows - diff --git a/app/test/task/testdata/goldens/packages/oxygen/versions/1.0.0/install.html b/app/test/task/testdata/goldens/packages/oxygen/versions/1.0.0/install.html index 0daa7500da..6f3a955629 100644 --- a/app/test/task/testdata/goldens/packages/oxygen/versions/1.0.0/install.html +++ b/app/test/task/testdata/goldens/packages/oxygen/versions/1.0.0/install.html @@ -32,7 +32,7 @@ - + @@ -156,13 +156,13 @@

Windows - diff --git a/app/test/task/testdata/goldens/packages/oxygen/versions/1.0.0/license.html b/app/test/task/testdata/goldens/packages/oxygen/versions/1.0.0/license.html index ba6dcc35df..f665ee288c 100644 --- a/app/test/task/testdata/goldens/packages/oxygen/versions/1.0.0/license.html +++ b/app/test/task/testdata/goldens/packages/oxygen/versions/1.0.0/license.html @@ -32,7 +32,7 @@ - + @@ -156,13 +156,13 @@

Windows - diff --git a/app/test/task/testdata/goldens/packages/oxygen/versions/1.0.0/score.html b/app/test/task/testdata/goldens/packages/oxygen/versions/1.0.0/score.html index a619308fd4..1728d3d75e 100644 --- a/app/test/task/testdata/goldens/packages/oxygen/versions/1.0.0/score.html +++ b/app/test/task/testdata/goldens/packages/oxygen/versions/1.0.0/score.html @@ -32,7 +32,7 @@ - + @@ -156,13 +156,13 @@

Windows - diff --git a/app/test/task/testdata/goldens/packages/oxygen/versions/2.0.0.html b/app/test/task/testdata/goldens/packages/oxygen/versions/2.0.0.html index 8a5cad3406..03a9451961 100644 --- a/app/test/task/testdata/goldens/packages/oxygen/versions/2.0.0.html +++ b/app/test/task/testdata/goldens/packages/oxygen/versions/2.0.0.html @@ -32,7 +32,7 @@ - + @@ -152,13 +152,13 @@

Windows - diff --git a/pkg/_pub_shared/lib/data/page_data.dart b/pkg/_pub_shared/lib/data/page_data.dart index 7a2965ead1..a7118e2065 100644 --- a/pkg/_pub_shared/lib/data/page_data.dart +++ b/pkg/_pub_shared/lib/data/page_data.dart @@ -49,7 +49,6 @@ class PageData { class PkgData { final String package; final String version; - final int likes; /// PublisherId of publisher that owns this package, `null` if the package /// isn't owned by a publisher. @@ -62,7 +61,6 @@ class PkgData { required this.version, required this.publisherId, required this.isDiscontinued, - required this.likes, required this.isLatest, }); diff --git a/pkg/_pub_shared/lib/data/page_data.g.dart b/pkg/_pub_shared/lib/data/page_data.g.dart index fae523bc3b..34c9893aff 100644 --- a/pkg/_pub_shared/lib/data/page_data.g.dart +++ b/pkg/_pub_shared/lib/data/page_data.g.dart @@ -29,14 +29,12 @@ PkgData _$PkgDataFromJson(Map json) => PkgData( version: json['version'] as String, publisherId: json['publisherId'] as String?, isDiscontinued: json['isDiscontinued'] as bool, - likes: (json['likes'] as num).toInt(), isLatest: json['isLatest'] as bool, ); Map _$PkgDataToJson(PkgData instance) => { 'package': instance.package, 'version': instance.version, - 'likes': instance.likes, if (instance.publisherId case final value?) 'publisherId': value, 'isDiscontinued': instance.isDiscontinued, 'isLatest': instance.isLatest, diff --git a/pkg/pub_integration/test/like_test.dart b/pkg/pub_integration/test/like_test.dart new file mode 100644 index 0000000000..769cd20a0f --- /dev/null +++ b/pkg/pub_integration/test/like_test.dart @@ -0,0 +1,67 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:pub_integration/src/fake_test_context_provider.dart'; +import 'package:pub_integration/src/test_browser.dart'; +import 'package:test/test.dart'; + +void main() { + group('like test', () { + late final TestContextProvider fakeTestScenario; + + setUpAll(() async { + fakeTestScenario = await TestContextProvider.start(); + }); + + tearDownAll(() async { + await fakeTestScenario.close(); + }); + + test('bulk tests', () async { + // init server data + await http.post( + Uri.parse('${fakeTestScenario.pubHostedUrl}/fake-test-profile'), + body: json.encode( + { + 'testProfile': { + 'defaultUser': 'admin@pub.dev', + 'generatedPackages': [ + {'name': 'test_pkg'}, + ], + }, + }, + ), + ); + + final user = await fakeTestScenario.createTestUser(email: 'user@pub.dev'); + + await user.withBrowserPage((page) async { + Future getCountLabel() async { + final label = await page.$('.like-button-and-label--count'); + return await label.textContent(); + } + + await page.gotoOrigin('/packages/test_pkg'); + expect(await getCountLabel(), '0'); + + await page.click('.like-button-and-label--button'); + await Future.delayed(Duration(seconds: 1)); + expect(await getCountLabel(), '1'); + + await page.gotoOrigin('/packages/test_pkg'); + expect(await getCountLabel(), '1'); + + await page.click('.like-button-and-label--button'); + await Future.delayed(Duration(seconds: 1)); + + expect(await getCountLabel(), '0'); + await page.gotoOrigin('/packages/test_pkg'); + expect(await getCountLabel(), '0'); + }); + }); + }); +} diff --git a/pkg/web_app/lib/src/likes.dart b/pkg/web_app/lib/src/likes.dart index 1fedd285d3..1c2a1ccd12 100644 --- a/pkg/web_app/lib/src/likes.dart +++ b/pkg/web_app/lib/src/likes.dart @@ -13,7 +13,6 @@ import 'package:mdc_web/mdc_web.dart' show MDCIconButtonToggle; import '_dom_helper.dart'; import 'account.dart'; import 'api_client/api_client.dart' deferred as api_client; -import 'page_data.dart'; Future _done = Future.value(); @@ -46,48 +45,56 @@ void setupLikesList() { } void setupLikes() { - final likes = document.querySelector('#likes-count'); - final likeButton = - document.querySelector('#-pub-like-icon-button') as ButtonElement?; + for (final buttonAndLabel + in document.querySelectorAll('.like-button-and-label')) { + final likeButton = buttonAndLabel + .querySelector('.like-button-and-label--button') as ButtonElement?; + if (likeButton == null) continue; - // If `likeButton` is not on this page we assume the page doesn't display a - // `like package` button. - if (likeButton == null) return; + final countLabel = + buttonAndLabel.querySelector('.like-button-and-label--count'); + if (countLabel == null) continue; - final iconButtonToggle = MDCIconButtonToggle(likeButton); - var likesDelta = 0; + final package = countLabel.dataset['package']; + if (package == null || package.isEmpty) continue; - // keep in-sync with app/lib/frontend/templates/views/shared/detail/header.dart - String likesString() { - final likesCount = pageData.pkgData!.likes + likesDelta; - return formatWithSuffix(likesCount); - } + final originalCount = int.tryParse(countLabel.dataset['value'] ?? ''); + if (originalCount == null) continue; - iconButtonToggle.listen(MDCIconButtonToggle.changeEvent, (Event e) async { - if (isNotAuthenticated) { - iconButtonToggle.on = false; - final content = ParagraphElement() - ..text = 'You need to be signed-in to like packages. ' - 'Would you like to visit the authentication page?'; - final redirect = await modalConfirm(content); - if (redirect) { - document.getElementById('-account-login')?.click(); - } - return; - } - likeButton.blur(); - await api_client.loadLibrary(); - if (iconButtonToggle.on ?? false) { - // The button has shifted to on. - likesDelta++; - likes!.innerText = likesString(); - _enqueue(() => api_client.client.likePackage(pageData.pkgData!.package)); - } else { - // The button has shifted to off. - likesDelta--; - likes!.innerText = likesString(); - _enqueue( - () => api_client.client.unlikePackage(pageData.pkgData!.package)); + var likesDelta = 0; + + // keep in-sync with app/lib/frontend/templates/views/pkg/liked_package_list.dart + String likesString() { + final likesCount = originalCount + likesDelta; + return formatWithSuffix(likesCount); } - }); + + final iconButtonToggle = MDCIconButtonToggle(likeButton); + iconButtonToggle.listen(MDCIconButtonToggle.changeEvent, (Event e) async { + if (isNotAuthenticated) { + iconButtonToggle.on = false; + final content = ParagraphElement() + ..text = 'You need to be signed-in to like packages. ' + 'Would you like to visit the authentication page?'; + final redirect = await modalConfirm(content); + if (redirect) { + document.getElementById('-account-login')?.click(); + } + return; + } + likeButton.blur(); + await api_client.loadLibrary(); + if (iconButtonToggle.on ?? false) { + // The button has shifted to on. + likesDelta++; + countLabel.innerText = likesString(); + _enqueue(() => api_client.client.likePackage(package)); + } else { + // The button has shifted to off. + likesDelta--; + countLabel.innerText = likesString(); + _enqueue(() => api_client.client.unlikePackage(package)); + } + }); + } } diff --git a/pkg/web_css/lib/src/_detail_page.scss b/pkg/web_css/lib/src/_detail_page.scss index 3c9af34eb8..60e4695d7c 100644 --- a/pkg/web_css/lib/src/_detail_page.scss +++ b/pkg/web_css/lib/src/_detail_page.scss @@ -174,26 +174,34 @@ $detail-tabs-tablet-width: calc(100% - 240px); .detail-tags { flex-grow: 1; } +} - .detail-like { - font-family: var(--pub-font-family-body); - font-size: 16px; - font-weight: 500; - text-transform: uppercase; - white-space: nowrap; - display: flex; - margin-top: -4px; - border: 1px solid transparent; - padding: 2px; +.like-button-and-label { + font-family: var(--pub-font-family-body); + font-size: 16px; + font-weight: 500; + text-transform: uppercase; + white-space: nowrap; + display: flex; + margin-top: -4px; + border: 1px solid transparent; + padding: 2px; - .likes-count { - display: inline-block; - padding-top: 6px; - } + .like-button-and-label--button { + /* Override wide material button styles*/ + width: 26px; + height: 26px; + padding: 4px; + margin: 0 6px 0 0; + } - &:focus-within { - border-color: var(--pub-neutral-textColor); - } + .like-button-and-label--count-wrapper { + display: inline-block; + padding-top: 6px; + } + + &:focus-within { + border-color: var(--pub-neutral-textColor); } } @@ -419,11 +427,3 @@ $detail-tabs-tablet-width: calc(100% - 240px); } } } - -#-pub-like-icon-button { - /* Override wide material button styles*/ - width: 26px; - height: 26px; - padding: 4px; - margin: 0 6px 0 0; -} From 08858d8af9b2d7f5202d25d832c09e6be166206a Mon Sep 17 00:00:00 2001 From: Istvan Soos Date: Wed, 13 Aug 2025 16:20:01 +0200 Subject: [PATCH 2/3] Also update other labels. --- app/lib/frontend/templates/package_misc.dart | 1 + .../templates/views/pkg/labeled_scores.dart | 14 ++++++---- .../templates/views/pkg/score_tab.dart | 9 ++++-- pkg/pub_integration/test/like_test.dart | 28 +++++++++++++------ pkg/web_app/lib/src/likes.dart | 26 +++++++++++++---- 5 files changed, 56 insertions(+), 22 deletions(-) diff --git a/app/lib/frontend/templates/package_misc.dart b/app/lib/frontend/templates/package_misc.dart index 9412b7f397..b7c973026b 100644 --- a/app/lib/frontend/templates/package_misc.dart +++ b/app/lib/frontend/templates/package_misc.dart @@ -200,6 +200,7 @@ d.Node replacedByLink(String replacedBy) { /// Renders the labeled scores widget (the score values in a compact layout). d.Node labeledScoresNodeFromPackageView(PackageView view, {String? version}) { return labeledScoresNode( + package: view.name, pkgScorePageUrl: urls.pkgScoreUrl(view.name, version: version), likeCount: view.likes, grantedPubPoints: view.grantedPubPoints, diff --git a/app/lib/frontend/templates/views/pkg/labeled_scores.dart b/app/lib/frontend/templates/views/pkg/labeled_scores.dart index 147278c7a8..7b89eb4bae 100644 --- a/app/lib/frontend/templates/views/pkg/labeled_scores.dart +++ b/app/lib/frontend/templates/views/pkg/labeled_scores.dart @@ -7,11 +7,15 @@ import 'package:_pub_shared/format/number_format.dart'; import '../../../dom/dom.dart' as d; d.Node labeledScoresNode({ + required String package, required String pkgScorePageUrl, required int likeCount, required int? grantedPubPoints, required int? thirtyDaysDownloads, }) { + final formattedLikes = compactFormat(likeCount); + final formattedDownloads = + thirtyDaysDownloads == null ? null : compactFormat(thirtyDaysDownloads); return d.a( classes: ['packages-scores'], href: pkgScorePageUrl, @@ -20,9 +24,10 @@ d.Node labeledScoresNode({ classes: ['packages-score', 'packages-score-like'], child: _labeledScore( 'likes', - '${compactFormat(likeCount).value}' - '${compactFormat(likeCount).suffix}', + // keep in-sync with pkg/web_app/lib/src/likes.dart + '${formattedLikes.value}${formattedLikes.suffix}', sign: ''), + attributes: {'data-package': package}, ), d.div( classes: ['packages-score', 'packages-score-health'], @@ -35,9 +40,8 @@ d.Node labeledScoresNode({ classes: ['packages-score', 'packages-score-downloads'], child: _labeledScore( 'downloads', - thirtyDaysDownloads != null - ? '${compactFormat(thirtyDaysDownloads).value}' - '${compactFormat(thirtyDaysDownloads).suffix}' + formattedDownloads != null + ? '${formattedDownloads.value}${formattedDownloads.suffix}' : null, sign: '', ), diff --git a/app/lib/frontend/templates/views/pkg/score_tab.dart b/app/lib/frontend/templates/views/pkg/score_tab.dart index aa2a3ce60e..33fcef5642 100644 --- a/app/lib/frontend/templates/views/pkg/score_tab.dart +++ b/app/lib/frontend/templates/views/pkg/score_tab.dart @@ -318,11 +318,13 @@ d.Node _likeKeyFigureNode(int? likeCount) { label: 'likes', ); } + final formatted = compactFormat(likeCount); + // keep in-sync with pkg/web_app/lib/src/likes.dart return _keyFigureNode( - value: '${compactFormat(likeCount).value}' - '${compactFormat(likeCount).suffix}', + value: '${formatted.value}${formatted.suffix}', supplemental: '', label: 'likes', + classes: ['score-key-figure--likes'], ); } @@ -367,9 +369,10 @@ d.Node _keyFigureNode({ required String value, required String supplemental, required String label, + List? classes, }) { return d.div( - classes: ['score-key-figure'], + classes: ['score-key-figure', ...?classes], children: [ d.div( classes: ['score-key-figure-title'], diff --git a/pkg/pub_integration/test/like_test.dart b/pkg/pub_integration/test/like_test.dart index 769cd20a0f..ce86e69835 100644 --- a/pkg/pub_integration/test/like_test.dart +++ b/pkg/pub_integration/test/like_test.dart @@ -40,27 +40,37 @@ void main() { final user = await fakeTestScenario.createTestUser(email: 'user@pub.dev'); await user.withBrowserPage((page) async { - Future getCountLabel() async { - final label = await page.$('.like-button-and-label--count'); - return await label.textContent(); + Future> getCountLabels() async { + final buttonLabel = await page.$('.like-button-and-label--count'); + final viewLabel = + await page.$('.packages-score-like .packages-score-value-number'); + final keyScoreLabel = await page + .$OrNull('.score-key-figure--likes .score-key-figure-value'); + return [ + await buttonLabel.textContent(), + await viewLabel.textContent(), + (await keyScoreLabel?.textContent()) ?? '', + ]; } await page.gotoOrigin('/packages/test_pkg'); - expect(await getCountLabel(), '0'); + expect(await getCountLabels(), ['0', '0', '']); await page.click('.like-button-and-label--button'); await Future.delayed(Duration(seconds: 1)); - expect(await getCountLabel(), '1'); + expect(await getCountLabels(), ['1', '1', '']); - await page.gotoOrigin('/packages/test_pkg'); - expect(await getCountLabel(), '1'); + // displaying all three + await page.gotoOrigin('/packages/test_pkg/score'); + expect(await getCountLabels(), ['1', '1', '1']); await page.click('.like-button-and-label--button'); await Future.delayed(Duration(seconds: 1)); - expect(await getCountLabel(), '0'); + // checking it on the main package page too + expect(await getCountLabels(), ['0', '0', '0']); await page.gotoOrigin('/packages/test_pkg'); - expect(await getCountLabel(), '0'); + expect(await getCountLabels(), ['0', '0', '']); }); }); }); diff --git a/pkg/web_app/lib/src/likes.dart b/pkg/web_app/lib/src/likes.dart index 1c2a1ccd12..7ac17735e4 100644 --- a/pkg/web_app/lib/src/likes.dart +++ b/pkg/web_app/lib/src/likes.dart @@ -63,10 +63,26 @@ void setupLikes() { var likesDelta = 0; - // keep in-sync with app/lib/frontend/templates/views/pkg/liked_package_list.dart - String likesString() { + void updateLabels() { final likesCount = originalCount + likesDelta; - return formatWithSuffix(likesCount); + // keep in-sync with app/lib/frontend/templates/views/pkg/liked_package_list.dart + countLabel.innerText = formatWithSuffix(likesCount); + + // keep in-sync with app/lib/frontend/templates/views/pkg/labeled_scores.dart + final formatted = compactFormat(likesCount); + final labeledScoreLikeString = '${formatted.value}${formatted.suffix}'; + final labeledLikes = querySelectorAll('.packages-score-like') + .where((e) => e.dataset['package'] == package) + .toList(); + for (final labeledLike in labeledLikes) { + labeledLike.querySelector('.packages-score-value-number')?.text = + labeledScoreLikeString; + } + + // keep in-sync with app/lib/frontend/templates/views/pkg/score_tab.dart + querySelector('.score-key-figure--likes') + ?.querySelector('.score-key-figure-value') + ?.text = labeledScoreLikeString; } final iconButtonToggle = MDCIconButtonToggle(likeButton); @@ -87,12 +103,12 @@ void setupLikes() { if (iconButtonToggle.on ?? false) { // The button has shifted to on. likesDelta++; - countLabel.innerText = likesString(); + updateLabels(); _enqueue(() => api_client.client.likePackage(package)); } else { // The button has shifted to off. likesDelta--; - countLabel.innerText = likesString(); + updateLabels(); _enqueue(() => api_client.client.unlikePackage(package)); } }); From f0e2a1b602d428215fdaa36d82415472fed6ac21 Mon Sep 17 00:00:00 2001 From: Istvan Soos Date: Wed, 13 Aug 2025 16:40:34 +0200 Subject: [PATCH 3/3] updated golden files --- app/test/frontend/golden/my_packages.html | 4 ++-- app/test/frontend/golden/pkg_activity_log_page.html | 4 ++-- app/test/frontend/golden/pkg_admin_page.html | 4 ++-- app/test/frontend/golden/pkg_changelog_page.html | 4 ++-- app/test/frontend/golden/pkg_example_page.html | 4 ++-- app/test/frontend/golden/pkg_index_page.html | 4 ++-- app/test/frontend/golden/pkg_install_page.html | 4 ++-- app/test/frontend/golden/pkg_score_page.html | 6 +++--- .../golden/pkg_score_page_with_downloads_chart.html | 6 +++--- app/test/frontend/golden/pkg_show_page.html | 4 ++-- app/test/frontend/golden/pkg_show_page_discontinued.html | 4 ++-- app/test/frontend/golden/pkg_show_page_flutter_plugin.html | 4 ++-- app/test/frontend/golden/pkg_show_page_publisher.html | 4 ++-- app/test/frontend/golden/pkg_show_page_retracted.html | 4 ++-- .../pkg_show_page_retracted_non_retracted_version.html | 4 ++-- app/test/frontend/golden/pkg_show_version_page.html | 4 ++-- app/test/frontend/golden/pkg_versions_page.html | 4 ++-- app/test/frontend/golden/publisher_packages_page.html | 4 ++-- .../frontend/golden/publisher_unlisted_packages_page.html | 4 ++-- app/test/frontend/golden/search_page.html | 4 ++-- app/test/task/testdata/goldens/packages/oxygen.html | 4 ++-- .../task/testdata/goldens/packages/oxygen/changelog.html | 4 ++-- app/test/task/testdata/goldens/packages/oxygen/example.html | 4 ++-- app/test/task/testdata/goldens/packages/oxygen/install.html | 4 ++-- app/test/task/testdata/goldens/packages/oxygen/license.html | 4 ++-- app/test/task/testdata/goldens/packages/oxygen/score.html | 6 +++--- .../task/testdata/goldens/packages/oxygen/versions.html | 4 ++-- .../testdata/goldens/packages/oxygen/versions/1.0.0.html | 4 ++-- .../goldens/packages/oxygen/versions/1.0.0/changelog.html | 4 ++-- .../goldens/packages/oxygen/versions/1.0.0/example.html | 4 ++-- .../goldens/packages/oxygen/versions/1.0.0/install.html | 4 ++-- .../goldens/packages/oxygen/versions/1.0.0/license.html | 4 ++-- .../goldens/packages/oxygen/versions/1.0.0/score.html | 6 +++--- .../testdata/goldens/packages/oxygen/versions/2.0.0.html | 4 ++-- 34 files changed, 72 insertions(+), 72 deletions(-) diff --git a/app/test/frontend/golden/my_packages.html b/app/test/frontend/golden/my_packages.html index 0b6951dd63..249ba29977 100644 --- a/app/test/frontend/golden/my_packages.html +++ b/app/test/frontend/golden/my_packages.html @@ -201,7 +201,7 @@

%%x-ago%% -
+
0 @@ -284,7 +284,7 @@

%%x-ago%%

-
+
0 diff --git a/app/test/frontend/golden/pkg_activity_log_page.html b/app/test/frontend/golden/pkg_activity_log_page.html index 5c4aaba17e..c29bde2de1 100644 --- a/app/test/frontend/golden/pkg_activity_log_page.html +++ b/app/test/frontend/golden/pkg_activity_log_page.html @@ -359,7 +359,7 @@

Metadata