diff --git a/README.md b/README.md index 2b059dcb..02f95ef1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 🛠️ ht_shared -![coverage: percentage](https://img.shields.io/badge/coverage-92-green) +![coverage: percentage](https://img.shields.io/badge/coverage-95-green) [![style: very good analysis](https://img.shields.io/badge/style-very_good_analysis-B22C89.svg)](https://pub.dev/packages/very_good_analysis) [![License: PolyForm Free Trial](https://img.shields.io/badge/License-PolyForm%20Free%20Trial-blue)](https://polyformproject.org/licenses/free-trial/1.0.0) @@ -11,24 +11,20 @@ Think of it as the common language 🗣️ that all parts of your news applicati ## 🌟 Core Benefits of `ht_shared` * **🧱 Unified Data Structure:** Guarantees that your `Headline`, `Source`, `User`, `FeedItem`, and configuration data are handled identically across the entire Headlines Toolkit. -* 💡 **Powering Intelligent Feeds:** Provides the foundational models and - configuration structures (like `EngagementRule`, `SuggestionRule`, - `EngagementContentTemplate`, `AppConfig` enhancements) that enable the - `ht_main` to deliver highly dynamic, personalized, and engaging content - feeds. Define *what* to show and *when* with enhanced flexibility! +* 💡 **Streamlined Feed Engagement:** Offers core models like `EngagementContent` and `Ad`, allowing your applications to seamlessly embed calls-to-action and advertisements. The logic for *when* and *how* these appear is now managed directly within your client applications, offering precise control over the user experience. * **🚀 Rapid Development:** Start building features faster with pre-defined, robust models for common news application needs. No reinventing the wheel! * **🔗 Seamless Integration:** Enables the Flutter mobile app, web dashboard, and Dart Frog API to work together flawlessly. * **🎯 Consistency by Design:** Reduces errors and simplifies maintenance by providing a single source of truth for core data definitions. -* **🌟 Foundation for Rich Features:** Includes models for user personalization (preferences, settings), dynamic feeds, and standardized API responses. +* **🌟 Foundation for Rich Features:** Includes models for user personalization (`UserContentPreferences`, `UserAppSettings`), dynamic feed items (`Ad`, `EngagementContent`), application-wide settings (`AppConfig`, `UserPreferenceConfig`, `AdConfig`), and standardized API responses. ## 🎁 Key Models Provided This package includes well-defined Dart classes for: * 📰 **News Content:** `Headline`, `Category`, `Source`, `Country` -* 🧩 **Feed System:** `FeedItem` (and its subtypes like `Ad`, `SuggestedContent`, `EngagementContent`), `FeedItemAction` -* 👤 **User Data:** `User`, `UserContentPreferences`, `UserAppSettings` -* ⚙️ **Application Configuration:** `AppConfig` +* 🧩 **Feed System:** `FeedItem` (and its subtypes `Ad`, `EngagementContent`), `FeedItemAction` +* 👤 **User Data:** `User`, `UserRole`, `Permission`, `UserContentPreferences`, `UserAppSettings` +* ⚙️ **Application Configuration:** `AppConfig` (containing `AdConfig` and `UserPreferenceConfig`) * 📡 **API Communication:** `PaginatedResponse`, `SuccessApiResponse`, and a comprehensive `HtHttpException` hierarchy for standardized error handling. ## 🔑 Access and Licensing @@ -60,4 +56,3 @@ To integrate `ht_shared` into a Headlines Toolkit component (or your custom Dart ```dart import 'package:ht_shared/ht_shared.dart'; - ``` diff --git a/lib/src/models/core/feed_item.dart b/lib/src/models/core/feed_item.dart index f041657e..68390a7b 100644 --- a/lib/src/models/core/feed_item.dart +++ b/lib/src/models/core/feed_item.dart @@ -6,7 +6,6 @@ import 'package:ht_shared/src/models/entities/headline.dart'; import 'package:ht_shared/src/models/entities/source.dart'; import 'package:ht_shared/src/models/feed_decorators/ad.dart'; import 'package:ht_shared/src/models/feed_decorators/engagement_content.dart'; -import 'package:ht_shared/src/models/feed_decorators/suggested_content.dart'; /// {@template feed_item} /// An abstract base class for all items that can appear in a mixed content @@ -51,8 +50,6 @@ abstract class FeedItem extends Equatable { return Country.fromJson(json); case 'ad': return Ad.fromJson(json); - case 'suggested_content': - return SuggestedContent.fromJson(json); case 'engagement_content': return EngagementContent.fromJson(json); default: diff --git a/lib/src/models/feed_decorators/ad.dart b/lib/src/models/feed_decorators/ad.dart index da4d023a..53cb883f 100644 --- a/lib/src/models/feed_decorators/ad.dart +++ b/lib/src/models/feed_decorators/ad.dart @@ -1,7 +1,6 @@ import 'package:ht_shared/src/models/core/feed_item.dart'; import 'package:ht_shared/src/models/core/feed_item_action.dart' show FeedItemAction, feedItemActionFromJson, feedItemActionToJson; -import 'package:ht_shared/src/models/feed_decorators/ad_placement.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; import 'package:uuid/uuid.dart'; @@ -26,6 +25,19 @@ enum AdType { interstitial, } +/// {@template ad_placement} +/// Defines specific, known locations or contexts where an ad might appear. +/// {@endtemplate} +@JsonEnum(fieldRename: FieldRename.snake) +enum AdPlacement { + /// A standard banner ad placed inline within the main feed. + feedInlineStandardBanner, + + /// A native ad designed to blend with content, placed inline within the main + /// feed. + feedInlineNativeBanner, +} + /// {@template ad} /// Represents an advertisement item that can appear in the feed. /// {@endtemplate} diff --git a/lib/src/models/feed_decorators/ad_placement.dart b/lib/src/models/feed_decorators/ad_placement.dart deleted file mode 100644 index d66a7939..00000000 --- a/lib/src/models/feed_decorators/ad_placement.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; - -/// {@template ad_placement} -/// Defines specific, known locations or contexts where an ad might appear. -/// {@endtemplate} -@JsonEnum(fieldRename: FieldRename.snake) -enum AdPlacement { - /// A standard banner ad placed inline within the main feed. - feedInlineStandardBanner, - - /// A native ad designed to blend with content, placed inline within the main - /// feed. - feedInlineNativeBanner, -} diff --git a/lib/src/models/feed_decorators/engagement_content.dart b/lib/src/models/feed_decorators/engagement_content.dart index bccea239..22ad9171 100644 --- a/lib/src/models/feed_decorators/engagement_content.dart +++ b/lib/src/models/feed_decorators/engagement_content.dart @@ -1,13 +1,27 @@ import 'package:ht_shared/src/models/core/feed_item.dart'; import 'package:ht_shared/src/models/core/feed_item_action.dart' show FeedItemAction, feedItemActionFromJson, feedItemActionToJson; -import 'package:ht_shared/src/models/feed_decorators/engagement_content_type.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; import 'package:uuid/uuid.dart'; part 'engagement_content.g.dart'; +/// {@template engagement_content_type} +/// Defines the specific type or purpose of an [EngagementContent] item. +/// {@endtemplate} +@JsonEnum(fieldRename: FieldRename.snake) +enum EngagementContentType { + /// A call-to-action to sign up for an account. + signUp, + + /// A call-to-action to upgrade to a premium subscription. + upgrade, + + /// A call-to-action to rate the application in an app store. + rateApp, +} + /// {@template engagement_content} /// A generic model for in-feed calls-to-action or engagement prompts. /// diff --git a/lib/src/models/feed_decorators/engagement_content.g.dart b/lib/src/models/feed_decorators/engagement_content.g.dart index 93d39adc..43b5fa15 100644 --- a/lib/src/models/feed_decorators/engagement_content.g.dart +++ b/lib/src/models/feed_decorators/engagement_content.g.dart @@ -51,9 +51,5 @@ Map _$EngagementContentToJson(EngagementContent instance) => const _$EngagementContentTypeEnumMap = { EngagementContentType.signUp: 'sign_up', EngagementContentType.upgrade: 'upgrade', - EngagementContentType.feedback: 'feedback', - EngagementContentType.survey: 'survey', EngagementContentType.rateApp: 'rate_app', - EngagementContentType.shareApp: 'share_app', - EngagementContentType.custom: 'custom', }; diff --git a/lib/src/models/feed_decorators/engagement_content_type.dart b/lib/src/models/feed_decorators/engagement_content_type.dart deleted file mode 100644 index 3462edb2..00000000 --- a/lib/src/models/feed_decorators/engagement_content_type.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:ht_shared/ht_shared.dart' show EngagementContent; -import 'package:ht_shared/src/models/feed_decorators/engagement_content.dart' - show EngagementContent; -import 'package:ht_shared/src/models/feed_decorators/feed_decorators.dart' - show EngagementContent; -import 'package:ht_shared/src/models/models.dart' show EngagementContent; -import 'package:json_annotation/json_annotation.dart'; - -/// {@template engagement_content_type} -/// Defines the specific type or purpose of an [EngagementContent] item. -/// {@endtemplate} -@JsonEnum(fieldRename: FieldRename.snake) -enum EngagementContentType { - /// A call-to-action to sign up for an account. - signUp, - - /// A call-to-action to upgrade to a premium subscription. - upgrade, - - /// A call-to-action to rate the application in an app store. - rateApp, - -} diff --git a/lib/src/models/feed_decorators/feed_decorators.dart b/lib/src/models/feed_decorators/feed_decorators.dart index 8dd3d5d5..53cbb96e 100644 --- a/lib/src/models/feed_decorators/feed_decorators.dart +++ b/lib/src/models/feed_decorators/feed_decorators.dart @@ -1,6 +1,2 @@ export 'ad.dart'; -export 'ad_placement.dart'; export 'engagement_content.dart'; -export 'engagement_content_type.dart'; -export 'suggested_content.dart'; -export 'suggested_content_display_type.dart'; diff --git a/lib/src/models/feed_decorators/suggested_content.dart b/lib/src/models/feed_decorators/suggested_content.dart deleted file mode 100644 index e330de00..00000000 --- a/lib/src/models/feed_decorators/suggested_content.dart +++ /dev/null @@ -1,125 +0,0 @@ -import 'package:ht_shared/ht_shared.dart' - show Category, Country, Headline, Source; -import 'package:ht_shared/src/models/core/feed_item.dart'; -import 'package:ht_shared/src/models/core/feed_item_action.dart' - show FeedItemAction, feedItemActionFromJson, feedItemActionToJson; -import 'package:ht_shared/src/models/entities/entities.dart' - show Category, Country, Headline, Source; -import 'package:ht_shared/src/models/feed_decorators/suggested_content_display_type.dart'; -import 'package:ht_shared/src/models/models.dart' - show Category, Country, Headline, Source; -import 'package:json_annotation/json_annotation.dart'; -import 'package:meta/meta.dart'; -import 'package:uuid/uuid.dart'; - -part 'suggested_content.g.dart'; - -/// {@template suggested_content} -/// A generic model for suggested items that can appear in the feed. -/// -/// This item can contain a list of various content types (e.g., [Headline], -/// [Category], [Source], [Country]), allowing for flexible suggestions. -/// The [displayType] field specifies how this suggestion block should be -/// visually presented in the UI. -/// {@endtemplate} -@immutable -@JsonSerializable( - fieldRename: FieldRename.snake, - explicitToJson: true, - includeIfNull: false, - checked: true, -) -class SuggestedContent extends FeedItem { - /// {@macro suggested_content} - SuggestedContent({ - required this.displayType, - required List items, - required FeedItemAction action, - this.title, - this.description, - String? id, - }) : id = id ?? const Uuid().v4(), - items = items, - action = action, - super(type: 'suggested_content', action: action); - - /// Factory method to create a [SuggestedContent] instance from a JSON map. - factory SuggestedContent.fromJson(Map json) => - _$SuggestedContentFromJson(json); - - /// Unique identifier for the suggested content block. - final String id; - - /// An optional title for the suggestion block (e.g., "You might like..."). - final String? title; - - /// An optional description for the suggestion block. - final String? description; - - /// The visual presentation or layout style for this suggestion block. - /// Will be null if an unknown value is encountered during deserialization. - - final SuggestedContentDisplayType? displayType; - - /// The list of actual suggested items. These are [FeedItem] instances. - @JsonKey(fromJson: _feedItemsFromJson, toJson: _feedItemsToJson) - final List items; // Changed from List - - /// The action to be performed when this feed item is interacted with. - @JsonKey(fromJson: feedItemActionFromJson, toJson: feedItemActionToJson) - @override - final FeedItemAction action; - - /// Converts this [SuggestedContent] instance to a JSON map. - @override - Map toJson() { - final json = _$SuggestedContentToJson(this); - // The 'type' field is already part of FeedItem and should be - // handled by its toJson method if _$SuggestedContentToJson doesn't - // already include it due to inheritance. - // However, explicitToJson: true should handle this. - // Let's ensure 'type' is present, as per original logic. - json['type'] = type; - return json; - } - - // Helper functions for custom serialization of List - static List _feedItemsFromJson(List jsonList) => jsonList - .map((itemJson) => FeedItem.fromJson(itemJson as Map)) - .toList(); - - static List> _feedItemsToJson(List items) => - items.map((item) => item.toJson()).toList(); - - @override - List get props => [ - id, - title, - description, - displayType, - items, - type, // from FeedItem - action, // from FeedItem - ]; - - /// Creates a new [SuggestedContent] with updated properties. - /// Use this to modify a [SuggestedContent] without changing the original - /// instance. - SuggestedContent copyWith({ - String? id, - String? title, - String? description, - SuggestedContentDisplayType? displayType, - List? items, // Changed from List - FeedItemAction? action, - }) { - return SuggestedContent( - id: id ?? this.id, - title: title ?? this.title, - description: description ?? this.description, - displayType: displayType ?? this.displayType, - items: items ?? this.items, - action: action ?? this.action, - ); - } -} diff --git a/lib/src/models/feed_decorators/suggested_content.g.dart b/lib/src/models/feed_decorators/suggested_content.g.dart deleted file mode 100644 index 1c7edb05..00000000 --- a/lib/src/models/feed_decorators/suggested_content.g.dart +++ /dev/null @@ -1,50 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'suggested_content.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -SuggestedContent _$SuggestedContentFromJson(Map json) => - $checkedCreate( - 'SuggestedContent', - json, - ($checkedConvert) { - final val = SuggestedContent( - displayType: $checkedConvert( - 'display_type', - (v) => - $enumDecodeNullable(_$SuggestedContentDisplayTypeEnumMap, v)), - items: $checkedConvert( - 'items', (v) => SuggestedContent._feedItemsFromJson(v as List)), - action: $checkedConvert('action', - (v) => feedItemActionFromJson(v as Map)), - title: $checkedConvert('title', (v) => v as String?), - description: $checkedConvert('description', (v) => v as String?), - id: $checkedConvert('id', (v) => v as String?), - ); - return val; - }, - fieldKeyMap: const {'displayType': 'display_type'}, - ); - -Map _$SuggestedContentToJson(SuggestedContent instance) => - { - 'id': instance.id, - if (instance.title case final value?) 'title': value, - if (instance.description case final value?) 'description': value, - if (_$SuggestedContentDisplayTypeEnumMap[instance.displayType] - case final value?) - 'display_type': value, - 'items': SuggestedContent._feedItemsToJson(instance.items), - 'action': feedItemActionToJson(instance.action), - }; - -const _$SuggestedContentDisplayTypeEnumMap = { - SuggestedContentDisplayType.horizontalCardList: 'horizontal_card_list', - SuggestedContentDisplayType.verticalCardList: 'vertical_card_list', - SuggestedContentDisplayType.grid: 'grid', - SuggestedContentDisplayType.singlePromotionalCard: 'single_promotional_card', - SuggestedContentDisplayType.textList: 'text_list', -}; diff --git a/lib/src/models/feed_decorators/suggested_content_display_type.dart b/lib/src/models/feed_decorators/suggested_content_display_type.dart deleted file mode 100644 index e89d4a35..00000000 --- a/lib/src/models/feed_decorators/suggested_content_display_type.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:ht_shared/ht_shared.dart' show SuggestedContent; -import 'package:ht_shared/src/models/feed_decorators/feed_decorators.dart' - show SuggestedContent; -import 'package:ht_shared/src/models/feed_decorators/suggested_content.dart' - show SuggestedContent; -import 'package:ht_shared/src/models/models.dart' show SuggestedContent; -import 'package:json_annotation/json_annotation.dart'; - -/// {@template suggested_content_display_type} -/// Defines the visual presentation or layout style for a -/// [SuggestedContent] block within the application feed. -/// -/// This enum guides the UI on how to render the list of items -/// contained within a SuggestedContent model. -/// {@endtemplate} -@JsonEnum(fieldRename: FieldRename.snake) -enum SuggestedContentDisplayType { - /// Displays content items in a horizontally scrollable list of cards. - horizontalCardList, - - /// Displays content items in a vertically stacked list of cards. - verticalCardList, - - /// Displays content items in a grid layout. - grid, - - /// Displays a single, prominent content item as a promotional card. - singlePromotionalCard, - - /// Displays content items as a simple list of text entries. - textList, -} diff --git a/lib/src/models/feed_extras/engagement_content_template.dart b/lib/src/models/feed_extras/engagement_content_template.dart deleted file mode 100644 index 2f71ddcd..00000000 --- a/lib/src/models/feed_extras/engagement_content_template.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:ht_shared/src/models/feed_extras/feed_template_types.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:meta/meta.dart'; - -part 'engagement_content_template.g.dart'; - -/// {@template engagement_content_template} -/// Defines the static content for an engagement prompt. -/// The 'type' of an instance should match an [EngagementTemplateType] value. -/// {@endtemplate} -@immutable -@JsonSerializable( - fieldRename: FieldRename.snake, - explicitToJson: true, - includeIfNull: false, - checked: true, -) -class EngagementContentTemplate extends Equatable { - /// {@macro engagement_content_template} - const EngagementContentTemplate({ - required this.type, - required this.title, - this.description, - this.callToActionText, - }); - - /// Creates an [EngagementContentTemplate] from JSON data. - factory EngagementContentTemplate.fromJson(Map json) => - _$EngagementContentTemplateFromJson(json); - - /// The type of engagement template, matching an [EngagementTemplateType] value. - - final EngagementTemplateType type; - - /// The main title or heading for the engagement content. - final String title; - - /// An optional description providing more details. - final String? description; - - /// The text for the call-to-action button or link. - final String? callToActionText; - - /// Converts this [EngagementContentTemplate] instance to JSON data. - Map toJson() => _$EngagementContentTemplateToJson(this); - - @override - List get props => [type, title, description, callToActionText]; - - /// Creates a copy of this [EngagementContentTemplate] but with the given - /// fields replaced with the new values. - EngagementContentTemplate copyWith({ - EngagementTemplateType? type, - String? title, - String? description, - String? callToActionText, - }) { - return EngagementContentTemplate( - type: type ?? this.type, - title: title ?? this.title, - description: description ?? this.description, - callToActionText: callToActionText ?? this.callToActionText, - ); - } -} diff --git a/lib/src/models/feed_extras/engagement_content_template.g.dart b/lib/src/models/feed_extras/engagement_content_template.g.dart deleted file mode 100644 index 88ae747a..00000000 --- a/lib/src/models/feed_extras/engagement_content_template.g.dart +++ /dev/null @@ -1,44 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'engagement_content_template.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -EngagementContentTemplate _$EngagementContentTemplateFromJson( - Map json) => - $checkedCreate( - 'EngagementContentTemplate', - json, - ($checkedConvert) { - final val = EngagementContentTemplate( - type: $checkedConvert( - 'type', (v) => $enumDecode(_$EngagementTemplateTypeEnumMap, v)), - title: $checkedConvert('title', (v) => v as String), - description: $checkedConvert('description', (v) => v as String?), - callToActionText: - $checkedConvert('call_to_action_text', (v) => v as String?), - ); - return val; - }, - fieldKeyMap: const {'callToActionText': 'call_to_action_text'}, - ); - -Map _$EngagementContentTemplateToJson( - EngagementContentTemplate instance) => - { - 'type': _$EngagementTemplateTypeEnumMap[instance.type]!, - 'title': instance.title, - if (instance.description case final value?) 'description': value, - if (instance.callToActionText case final value?) - 'call_to_action_text': value, - }; - -const _$EngagementTemplateTypeEnumMap = { - EngagementTemplateType.rateApp: 'rate_app', - EngagementTemplateType.linkAccount: 'link_account', - EngagementTemplateType.upgradeToPremium: 'upgrade_to_premium', - EngagementTemplateType.completeProfile: 'complete_profile', - EngagementTemplateType.exploreNewFeature: 'explore_new_feature', -}; diff --git a/lib/src/models/feed_extras/feed_extras.dart b/lib/src/models/feed_extras/feed_extras.dart deleted file mode 100644 index 8cb89a38..00000000 --- a/lib/src/models/feed_extras/feed_extras.dart +++ /dev/null @@ -1,3 +0,0 @@ -export 'engagement_content_template.dart'; -export 'feed_template_types.dart'; -export 'suggested_content_template.dart'; diff --git a/lib/src/models/feed_extras/feed_template_types.dart b/lib/src/models/feed_extras/feed_template_types.dart deleted file mode 100644 index b5aea9d7..00000000 --- a/lib/src/models/feed_extras/feed_template_types.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; - -/// Defines the types of engagement content templates available. -/// The string value of the enum (e.g., 'rate-app') will be used as the ID -/// to link rules in AppConfig to specific EngagementContentTemplate instances. -@JsonEnum(fieldRename: FieldRename.snake) -enum EngagementTemplateType { - /// Prompt to rate the application. - rateApp, - - /// Prompt for guest users to create/link an account. - linkAccount, - - /// Prompt for standard users to upgrade to premium. - upgradeToPremium, - - /// Prompt to complete user profile (e.g., select content preferences). - completeProfile, - - /// Prompt to explore a new feature. - exploreNewFeature, -} - -/// Defines the types of suggested content templates available. -/// The string value of the enum (e.g., 'categories-to-follow') will be used as the ID -/// to link rules in AppConfig to specific SuggestedContentTemplate instances. -@JsonEnum(fieldRename: FieldRename.snake) -enum SuggestionTemplateType { - /// Suggest categories to follow. - categoriesToFollow, - - /// Suggest sources to follow. - sourcesToFollow, - - /// Suggest countries to follow for news. - countriesToFollow, -} diff --git a/lib/src/models/feed_extras/suggested_content_template.dart b/lib/src/models/feed_extras/suggested_content_template.dart deleted file mode 100644 index 85a13485..00000000 --- a/lib/src/models/feed_extras/suggested_content_template.dart +++ /dev/null @@ -1,98 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:ht_shared/src/models/core/content_type.dart'; -import 'package:ht_shared/src/models/feed_decorators/suggested_content_display_type.dart'; -import 'package:ht_shared/src/models/feed_extras/feed_template_types.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:meta/meta.dart'; - -part 'suggested_content_template.g.dart'; - -/// {@template suggested_content_template} -/// Defines the static content and configuration for a suggestion block. -/// The 'type' of an instance should match a [SuggestionTemplateType] value. -/// {@endtemplate} -@immutable -@JsonSerializable( - fieldRename: FieldRename.snake, - explicitToJson: true, - includeIfNull: false, - checked: true, -) -class SuggestedContentTemplate extends Equatable { - /// {@macro suggested_content_template} - const SuggestedContentTemplate({ - required this.type, - required this.displayType, - required this.suggestedContentType, - this.title, - this.description, - this.maxItemsToDisplay, - this.fetchCriteria, - }); - - /// Creates a [SuggestedContentTemplate] from JSON data. - factory SuggestedContentTemplate.fromJson(Map json) => - _$SuggestedContentTemplateFromJson(json); - - /// The type of suggestion template, matching a [SuggestionTemplateType] value. - - final SuggestionTemplateType type; - - /// An optional title for the suggestion block (e.g., "You might like..."). - final String? title; - - /// An optional description for the suggestion block. - final String? description; - - /// The visual presentation or layout style for this suggestion block. - - final SuggestedContentDisplayType displayType; - - /// Defines what kind of primary content this suggestion block will contain - /// (e.g., if suggesting categories, this would be [ContentType.category]). - - final ContentType suggestedContentType; - - /// Maximum number of items to display within this suggestion block. - final int? maxItemsToDisplay; - - /// Criteria for fetching dynamic items, e.g., "popular", "newest". - /// This is a simple string; the decorator will interpret it. - final String? fetchCriteria; - - /// Converts this [SuggestedContentTemplate] instance to JSON data. - Map toJson() => _$SuggestedContentTemplateToJson(this); - - @override - List get props => [ - type, - title, - description, - displayType, - suggestedContentType, - maxItemsToDisplay, - fetchCriteria, - ]; - - /// Creates a copy of this [SuggestedContentTemplate] but with the given - /// fields replaced with the new values. - SuggestedContentTemplate copyWith({ - SuggestionTemplateType? type, - String? title, - String? description, - SuggestedContentDisplayType? displayType, - ContentType? suggestedContentType, - int? maxItemsToDisplay, - String? fetchCriteria, - }) { - return SuggestedContentTemplate( - type: type ?? this.type, - title: title ?? this.title, - description: description ?? this.description, - displayType: displayType ?? this.displayType, - suggestedContentType: suggestedContentType ?? this.suggestedContentType, - maxItemsToDisplay: maxItemsToDisplay ?? this.maxItemsToDisplay, - fetchCriteria: fetchCriteria ?? this.fetchCriteria, - ); - } -} diff --git a/lib/src/models/feed_extras/suggested_content_template.g.dart b/lib/src/models/feed_extras/suggested_content_template.g.dart deleted file mode 100644 index dd4ae562..00000000 --- a/lib/src/models/feed_extras/suggested_content_template.g.dart +++ /dev/null @@ -1,72 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'suggested_content_template.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -SuggestedContentTemplate _$SuggestedContentTemplateFromJson( - Map json) => - $checkedCreate( - 'SuggestedContentTemplate', - json, - ($checkedConvert) { - final val = SuggestedContentTemplate( - type: $checkedConvert( - 'type', (v) => $enumDecode(_$SuggestionTemplateTypeEnumMap, v)), - displayType: $checkedConvert('display_type', - (v) => $enumDecode(_$SuggestedContentDisplayTypeEnumMap, v)), - suggestedContentType: $checkedConvert('suggested_content_type', - (v) => $enumDecode(_$ContentTypeEnumMap, v)), - title: $checkedConvert('title', (v) => v as String?), - description: $checkedConvert('description', (v) => v as String?), - maxItemsToDisplay: $checkedConvert( - 'max_items_to_display', (v) => (v as num?)?.toInt()), - fetchCriteria: $checkedConvert('fetch_criteria', (v) => v as String?), - ); - return val; - }, - fieldKeyMap: const { - 'displayType': 'display_type', - 'suggestedContentType': 'suggested_content_type', - 'maxItemsToDisplay': 'max_items_to_display', - 'fetchCriteria': 'fetch_criteria' - }, - ); - -Map _$SuggestedContentTemplateToJson( - SuggestedContentTemplate instance) => - { - 'type': _$SuggestionTemplateTypeEnumMap[instance.type]!, - if (instance.title case final value?) 'title': value, - if (instance.description case final value?) 'description': value, - 'display_type': - _$SuggestedContentDisplayTypeEnumMap[instance.displayType]!, - 'suggested_content_type': - _$ContentTypeEnumMap[instance.suggestedContentType]!, - if (instance.maxItemsToDisplay case final value?) - 'max_items_to_display': value, - if (instance.fetchCriteria case final value?) 'fetch_criteria': value, - }; - -const _$SuggestionTemplateTypeEnumMap = { - SuggestionTemplateType.categoriesToFollow: 'categories_to_follow', - SuggestionTemplateType.sourcesToFollow: 'sources_to_follow', - SuggestionTemplateType.countriesToFollow: 'countries_to_follow', -}; - -const _$SuggestedContentDisplayTypeEnumMap = { - SuggestedContentDisplayType.horizontalCardList: 'horizontal_card_list', - SuggestedContentDisplayType.verticalCardList: 'vertical_card_list', - SuggestedContentDisplayType.grid: 'grid', - SuggestedContentDisplayType.singlePromotionalCard: 'single_promotional_card', - SuggestedContentDisplayType.textList: 'text_list', -}; - -const _$ContentTypeEnumMap = { - ContentType.headline: 'headline', - ContentType.category: 'category', - ContentType.source: 'source', - ContentType.country: 'country', -}; diff --git a/lib/src/models/models.dart b/lib/src/models/models.dart index 1df847d2..d5900c84 100644 --- a/lib/src/models/models.dart +++ b/lib/src/models/models.dart @@ -2,7 +2,6 @@ export 'auth/auth.dart'; export 'core/core.dart'; export 'entities/entities.dart'; export 'feed_decorators/feed_decorators.dart'; -export 'feed_extras/feed_extras.dart'; export 'remote_config/remote_config.dart'; export 'responses/responses.dart'; export 'user_preferences/user_preferences.dart'; diff --git a/lib/src/models/remote_config/app_config.dart b/lib/src/models/remote_config/app_config.dart index 8cd46cee..44c6bc4d 100644 --- a/lib/src/models/remote_config/app_config.dart +++ b/lib/src/models/remote_config/app_config.dart @@ -1,7 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:ht_shared/src/models/remote_config/ad_config.dart'; -import 'package:ht_shared/src/models/remote_config/feed_rules.dart'; -import 'package:ht_shared/src/models/remote_config/user_preference_limits.dart'; +import 'package:ht_shared/src/models/remote_config/user_preference_config.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; @@ -35,12 +34,10 @@ class AppConfig extends Equatable { /// Provides sensible defaults for nested configuration models if not specified. const AppConfig({ required this.id, - UserPreferenceLimits? userPreferenceLimits, + UserPreferenceConfig? userPreferenceLimits, AdConfig? adConfig, - List? engagementRules, - List? suggestionRules, }) : userPreferenceLimits = userPreferenceLimits ?? - const UserPreferenceLimits( + const UserPreferenceConfig( guestFollowedItemsLimit: 5, guestSavedHeadlinesLimit: 10, authenticatedFollowedItemsLimit: 15, @@ -56,9 +53,7 @@ class AppConfig extends Equatable { authenticatedAdPlacementInterval: 5, premiumAdFrequency: 0, // No ads for premium users by default premiumAdPlacementInterval: 0, - ), // Default ad config - engagementRules = engagementRules ?? const [], - suggestionRules = suggestionRules ?? const []; + ); // Default ad config /// Factory method to create an [AppConfig] instance from a JSON map. factory AppConfig.fromJson(Map json) => @@ -70,18 +65,12 @@ class AppConfig extends Equatable { /// Defines the maximum number of items a user can follow or save, /// tiered by user role. - final UserPreferenceLimits userPreferenceLimits; + final UserPreferenceConfig userPreferenceLimits; /// Defines configuration settings related to ad injection and display, /// tiered by user role. final AdConfig adConfig; - /// Defines rules for triggering engagement prompts. - final List engagementRules; - - /// Defines rules for triggering content suggestion blocks. - final List suggestionRules; - /// Converts this [AppConfig] instance to a JSON map. Map toJson() => _$AppConfigToJson(this); @@ -90,8 +79,6 @@ class AppConfig extends Equatable { id, userPreferenceLimits, adConfig, - engagementRules, - suggestionRules, ]; @override diff --git a/lib/src/models/remote_config/app_config.g.dart b/lib/src/models/remote_config/app_config.g.dart index 7d5567db..df41fcf2 100644 --- a/lib/src/models/remote_config/app_config.g.dart +++ b/lib/src/models/remote_config/app_config.g.dart @@ -16,32 +16,18 @@ AppConfig _$AppConfigFromJson(Map json) => $checkedCreate( 'user_preference_limits', (v) => v == null ? null - : UserPreferenceLimits.fromJson(v as Map)), + : UserPreferenceConfig.fromJson(v as Map)), adConfig: $checkedConvert( 'ad_config', (v) => v == null ? null : AdConfig.fromJson(v as Map)), - engagementRules: $checkedConvert( - 'engagement_rules', - (v) => (v as List?) - ?.map( - (e) => EngagementRule.fromJson(e as Map)) - .toList()), - suggestionRules: $checkedConvert( - 'suggestion_rules', - (v) => (v as List?) - ?.map( - (e) => SuggestionRule.fromJson(e as Map)) - .toList()), ); return val; }, fieldKeyMap: const { 'userPreferenceLimits': 'user_preference_limits', - 'adConfig': 'ad_config', - 'engagementRules': 'engagement_rules', - 'suggestionRules': 'suggestion_rules' + 'adConfig': 'ad_config' }, ); @@ -49,8 +35,4 @@ Map _$AppConfigToJson(AppConfig instance) => { 'id': instance.id, 'user_preference_limits': instance.userPreferenceLimits.toJson(), 'ad_config': instance.adConfig.toJson(), - 'engagement_rules': - instance.engagementRules.map((e) => e.toJson()).toList(), - 'suggestion_rules': - instance.suggestionRules.map((e) => e.toJson()).toList(), }; diff --git a/lib/src/models/remote_config/feed_rules.dart b/lib/src/models/remote_config/feed_rules.dart deleted file mode 100644 index a793b658..00000000 --- a/lib/src/models/remote_config/feed_rules.dart +++ /dev/null @@ -1,203 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:ht_shared/src/models/auth/user_role.dart'; -import 'package:ht_shared/src/models/feed_extras/feed_template_types.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:meta/meta.dart'; - -part 'feed_rules.g.dart'; - -/// {@template placement_criteria} -/// Defines where and how often an injected item should appear in a feed. -/// {@endtemplate} -@immutable -@JsonSerializable( - fieldRename: FieldRename.snake, - explicitToJson: true, - includeIfNull: false, - checked: true, -) -class PlacementCriteria extends Equatable { - /// {@macro placement_criteria} - const PlacementCriteria({ - this.afterPrimaryItemIndex, - this.relativePosition, - this.minPrimaryItemsRequired, - }); - - /// Creates a [PlacementCriteria] from JSON data. - factory PlacementCriteria.fromJson(Map json) => - _$PlacementCriteriaFromJson(json); - - /// Inject after this many primary feed items (0-indexed). - /// If null, no specific index is preferred. - final int? afterPrimaryItemIndex; - - /// A string indicating a general position, e.g., "middle", "end_quarter". - /// The decorator will interpret this. - final String? relativePosition; - - /// Minimum number of primary items required on the page for this injection - /// to be considered. Prevents injecting into very short lists. - final int? minPrimaryItemsRequired; - - /// Converts this [PlacementCriteria] instance to JSON data. - Map toJson() => _$PlacementCriteriaToJson(this); - - @override - List get props => [ - afterPrimaryItemIndex, - relativePosition, - minPrimaryItemsRequired, - ]; - - /// Creates a copy of this [PlacementCriteria] but with the given fields - /// replaced with the new values. - PlacementCriteria copyWith({ - int? afterPrimaryItemIndex, - String? relativePosition, - int? minPrimaryItemsRequired, - }) { - return PlacementCriteria( - afterPrimaryItemIndex: - afterPrimaryItemIndex ?? this.afterPrimaryItemIndex, - relativePosition: relativePosition ?? this.relativePosition, - minPrimaryItemsRequired: - minPrimaryItemsRequired ?? this.minPrimaryItemsRequired, - ); - } -} - -/// {@template engagement_rule} -/// Defines the rules for triggering a specific engagement prompt. -/// {@endtemplate} -@immutable -@JsonSerializable( - fieldRename: FieldRename.snake, - explicitToJson: true, - includeIfNull: false, - checked: true, -) -class EngagementRule extends Equatable { - /// {@macro engagement_rule} - const EngagementRule({ - required this.templateType, - required this.userRoles, - this.minDaysSinceAccountCreation, - this.maxTimesToShow, - this.minDaysSinceLastShown, - this.placement, - }); - - /// Creates an [EngagementRule] from JSON data. - factory EngagementRule.fromJson(Map json) => - _$EngagementRuleFromJson(json); - - /// Type of engagement template to use. - - final EngagementTemplateType templateType; - - /// Roles this rule applies to. - - final List userRoles; - - /// Minimum days since user account was created for this rule to apply. - final int? minDaysSinceAccountCreation; - - /// Overall maximum number of times this specific engagement can be shown to a user. - final int? maxTimesToShow; - - /// Minimum days since this specific engagement was last shown to the user. - final int? minDaysSinceLastShown; - - /// How to place this in the feed. - final PlacementCriteria? placement; - - /// Converts this [EngagementRule] instance to JSON data. - Map toJson() => _$EngagementRuleToJson(this); - - @override - List get props => [ - templateType, - userRoles, - minDaysSinceAccountCreation, - maxTimesToShow, - minDaysSinceLastShown, - placement, - ]; - - /// Creates a copy of this [EngagementRule] but with the given fields - /// replaced with the new values. - EngagementRule copyWith({ - EngagementTemplateType? templateType, - List? userRoles, - int? minDaysSinceAccountCreation, - int? maxTimesToShow, - int? minDaysSinceLastShown, - PlacementCriteria? placement, - }) { - return EngagementRule( - templateType: templateType ?? this.templateType, - userRoles: userRoles ?? this.userRoles, - minDaysSinceAccountCreation: - minDaysSinceAccountCreation ?? this.minDaysSinceAccountCreation, - maxTimesToShow: maxTimesToShow ?? this.maxTimesToShow, - minDaysSinceLastShown: - minDaysSinceLastShown ?? this.minDaysSinceLastShown, - placement: placement ?? this.placement, - ); - } -} - -/// {@template suggestion_rule} -/// Defines the rules for triggering a specific content suggestion block. -/// {@endtemplate} -@immutable -@JsonSerializable( - fieldRename: FieldRename.snake, - explicitToJson: true, - includeIfNull: false, - checked: true, -) -class SuggestionRule extends Equatable { - /// {@macro suggestion_rule} - const SuggestionRule({ - required this.templateType, - required this.userRoles, - this.placement, - }); - - /// Creates a [SuggestionRule] from JSON data. - factory SuggestionRule.fromJson(Map json) => - _$SuggestionRuleFromJson(json); - - /// Type of suggestion template to use. - - final SuggestionTemplateType templateType; - - /// Roles this rule applies to. - - final List userRoles; - - /// How to place this in the feed. - final PlacementCriteria? placement; - - /// Converts this [SuggestionRule] instance to JSON data. - Map toJson() => _$SuggestionRuleToJson(this); - - @override - List get props => [templateType, userRoles, placement]; - - /// Creates a copy of this [SuggestionRule] but with the given fields - /// replaced with the new values. - SuggestionRule copyWith({ - SuggestionTemplateType? templateType, - List? userRoles, - PlacementCriteria? placement, - }) { - return SuggestionRule( - templateType: templateType ?? this.templateType, - userRoles: userRoles ?? this.userRoles, - placement: placement ?? this.placement, - ); - } -} diff --git a/lib/src/models/remote_config/feed_rules.g.dart b/lib/src/models/remote_config/feed_rules.g.dart deleted file mode 100644 index c5495712..00000000 --- a/lib/src/models/remote_config/feed_rules.g.dart +++ /dev/null @@ -1,144 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'feed_rules.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -PlacementCriteria _$PlacementCriteriaFromJson(Map json) => - $checkedCreate( - 'PlacementCriteria', - json, - ($checkedConvert) { - final val = PlacementCriteria( - afterPrimaryItemIndex: $checkedConvert( - 'after_primary_item_index', (v) => (v as num?)?.toInt()), - relativePosition: - $checkedConvert('relative_position', (v) => v as String?), - minPrimaryItemsRequired: $checkedConvert( - 'min_primary_items_required', (v) => (v as num?)?.toInt()), - ); - return val; - }, - fieldKeyMap: const { - 'afterPrimaryItemIndex': 'after_primary_item_index', - 'relativePosition': 'relative_position', - 'minPrimaryItemsRequired': 'min_primary_items_required' - }, - ); - -Map _$PlacementCriteriaToJson(PlacementCriteria instance) => - { - if (instance.afterPrimaryItemIndex case final value?) - 'after_primary_item_index': value, - if (instance.relativePosition case final value?) - 'relative_position': value, - if (instance.minPrimaryItemsRequired case final value?) - 'min_primary_items_required': value, - }; - -EngagementRule _$EngagementRuleFromJson(Map json) => - $checkedCreate( - 'EngagementRule', - json, - ($checkedConvert) { - final val = EngagementRule( - templateType: $checkedConvert('template_type', - (v) => $enumDecode(_$EngagementTemplateTypeEnumMap, v)), - userRoles: $checkedConvert( - 'user_roles', - (v) => (v as List) - .map((e) => $enumDecode(_$UserRoleEnumMap, e)) - .toList()), - minDaysSinceAccountCreation: $checkedConvert( - 'min_days_since_account_creation', (v) => (v as num?)?.toInt()), - maxTimesToShow: - $checkedConvert('max_times_to_show', (v) => (v as num?)?.toInt()), - minDaysSinceLastShown: $checkedConvert( - 'min_days_since_last_shown', (v) => (v as num?)?.toInt()), - placement: $checkedConvert( - 'placement', - (v) => v == null - ? null - : PlacementCriteria.fromJson(v as Map)), - ); - return val; - }, - fieldKeyMap: const { - 'templateType': 'template_type', - 'userRoles': 'user_roles', - 'minDaysSinceAccountCreation': 'min_days_since_account_creation', - 'maxTimesToShow': 'max_times_to_show', - 'minDaysSinceLastShown': 'min_days_since_last_shown' - }, - ); - -Map _$EngagementRuleToJson(EngagementRule instance) => - { - 'template_type': _$EngagementTemplateTypeEnumMap[instance.templateType]!, - 'user_roles': - instance.userRoles.map((e) => _$UserRoleEnumMap[e]!).toList(), - if (instance.minDaysSinceAccountCreation case final value?) - 'min_days_since_account_creation': value, - if (instance.maxTimesToShow case final value?) 'max_times_to_show': value, - if (instance.minDaysSinceLastShown case final value?) - 'min_days_since_last_shown': value, - if (instance.placement?.toJson() case final value?) 'placement': value, - }; - -const _$EngagementTemplateTypeEnumMap = { - EngagementTemplateType.rateApp: 'rate_app', - EngagementTemplateType.linkAccount: 'link_account', - EngagementTemplateType.upgradeToPremium: 'upgrade_to_premium', - EngagementTemplateType.completeProfile: 'complete_profile', - EngagementTemplateType.exploreNewFeature: 'explore_new_feature', -}; - -const _$UserRoleEnumMap = { - UserRole.admin: 'admin', - UserRole.premiumUser: 'premium_user', - UserRole.standardUser: 'standard_user', - UserRole.guestUser: 'guest_user', -}; - -SuggestionRule _$SuggestionRuleFromJson(Map json) => - $checkedCreate( - 'SuggestionRule', - json, - ($checkedConvert) { - final val = SuggestionRule( - templateType: $checkedConvert('template_type', - (v) => $enumDecode(_$SuggestionTemplateTypeEnumMap, v)), - userRoles: $checkedConvert( - 'user_roles', - (v) => (v as List) - .map((e) => $enumDecode(_$UserRoleEnumMap, e)) - .toList()), - placement: $checkedConvert( - 'placement', - (v) => v == null - ? null - : PlacementCriteria.fromJson(v as Map)), - ); - return val; - }, - fieldKeyMap: const { - 'templateType': 'template_type', - 'userRoles': 'user_roles' - }, - ); - -Map _$SuggestionRuleToJson(SuggestionRule instance) => - { - 'template_type': _$SuggestionTemplateTypeEnumMap[instance.templateType]!, - 'user_roles': - instance.userRoles.map((e) => _$UserRoleEnumMap[e]!).toList(), - if (instance.placement?.toJson() case final value?) 'placement': value, - }; - -const _$SuggestionTemplateTypeEnumMap = { - SuggestionTemplateType.categoriesToFollow: 'categories_to_follow', - SuggestionTemplateType.sourcesToFollow: 'sources_to_follow', - SuggestionTemplateType.countriesToFollow: 'countries_to_follow', -}; diff --git a/lib/src/models/remote_config/remote_config.dart b/lib/src/models/remote_config/remote_config.dart index c0786ef5..609d1e20 100644 --- a/lib/src/models/remote_config/remote_config.dart +++ b/lib/src/models/remote_config/remote_config.dart @@ -1,5 +1,3 @@ export 'ad_config.dart'; export 'app_config.dart'; -export 'feed_rules.dart'; -export 'remote_config.dart'; -export 'user_preference_limits.dart'; +export 'user_preference_config.dart'; diff --git a/lib/src/models/remote_config/user_preference_limits.dart b/lib/src/models/remote_config/user_preference_config.dart similarity index 87% rename from lib/src/models/remote_config/user_preference_limits.dart rename to lib/src/models/remote_config/user_preference_config.dart index 7bff9501..e34fcd5e 100644 --- a/lib/src/models/remote_config/user_preference_limits.dart +++ b/lib/src/models/remote_config/user_preference_config.dart @@ -6,9 +6,9 @@ import 'package:ht_shared/src/models/models.dart' import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; -part 'user_preference_limits.g.dart'; +part 'user_preference_config.g.dart'; -/// {@template user_preference_limits} +/// {@template user_preference_config} /// Defines the maximum number of items a user can follow or save, /// tiered by user role (Guest, Authenticated, Premium). /// @@ -47,9 +47,9 @@ part 'user_preference_limits.g.dart'; includeIfNull: false, checked: true, ) -class UserPreferenceLimits extends Equatable { - /// {@macro user_preference_limits} - const UserPreferenceLimits({ +class UserPreferenceConfig extends Equatable { + /// {@macro user_preference_config} + const UserPreferenceConfig({ required this.guestFollowedItemsLimit, required this.guestSavedHeadlinesLimit, required this.authenticatedFollowedItemsLimit, @@ -58,9 +58,9 @@ class UserPreferenceLimits extends Equatable { required this.premiumSavedHeadlinesLimit, }); - /// Factory method to create a [UserPreferenceLimits] instance from a JSON map. - factory UserPreferenceLimits.fromJson(Map json) => - _$UserPreferenceLimitsFromJson(json); + /// Factory method to create a [UserPreferenceConfig] instance from a JSON map. + factory UserPreferenceConfig.fromJson(Map json) => + _$UserPreferenceConfigFromJson(json); /// Maximum number of countries, sources, or categories a Guest user can follow. /// This limit applies individually to each list. @@ -83,8 +83,8 @@ class UserPreferenceLimits extends Equatable { /// Maximum number of headlines a Premium user can save. final int premiumSavedHeadlinesLimit; - /// Converts this [UserPreferenceLimits] instance to a JSON map. - Map toJson() => _$UserPreferenceLimitsToJson(this); + /// Converts this [UserPreferenceConfig] instance to a JSON map. + Map toJson() => _$UserPreferenceConfigToJson(this); @override List get props => [ diff --git a/lib/src/models/remote_config/user_preference_limits.g.dart b/lib/src/models/remote_config/user_preference_config.g.dart similarity index 89% rename from lib/src/models/remote_config/user_preference_limits.g.dart rename to lib/src/models/remote_config/user_preference_config.g.dart index e19de21a..a9f47538 100644 --- a/lib/src/models/remote_config/user_preference_limits.g.dart +++ b/lib/src/models/remote_config/user_preference_config.g.dart @@ -1,18 +1,18 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'user_preference_limits.dart'; +part of 'user_preference_config.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** -UserPreferenceLimits _$UserPreferenceLimitsFromJson( +UserPreferenceConfig _$UserPreferenceConfigFromJson( Map json) => $checkedCreate( - 'UserPreferenceLimits', + 'UserPreferenceConfig', json, ($checkedConvert) { - final val = UserPreferenceLimits( + final val = UserPreferenceConfig( guestFollowedItemsLimit: $checkedConvert( 'guest_followed_items_limit', (v) => (v as num).toInt()), guestSavedHeadlinesLimit: $checkedConvert( @@ -39,8 +39,8 @@ UserPreferenceLimits _$UserPreferenceLimitsFromJson( }, ); -Map _$UserPreferenceLimitsToJson( - UserPreferenceLimits instance) => +Map _$UserPreferenceConfigToJson( + UserPreferenceConfig instance) => { 'guest_followed_items_limit': instance.guestFollowedItemsLimit, 'guest_saved_headlines_limit': instance.guestSavedHeadlinesLimit, diff --git a/test/src/models/core/feed_item_test.dart b/test/src/models/core/feed_item_test.dart index 6eff9456..b265efc3 100644 --- a/test/src/models/core/feed_item_test.dart +++ b/test/src/models/core/feed_item_test.dart @@ -39,13 +39,6 @@ void main() { placement: AdPlacement.feedInlineStandardBanner, action: defaultAction, ); - final mockSuggestedContent = SuggestedContent( - id: 'suggested-1', - title: 'Suggested for you', - displayType: SuggestedContentDisplayType.horizontalCardList, - items: [mockHeadline, mockCategory], - action: defaultAction, - ); final mockEngagementContent = EngagementContent( id: 'engagement-1', title: 'Sign Up Now', @@ -89,13 +82,6 @@ void main() { expect(feedItem, equals(mockAd)); }); - test('dispatches to SuggestedContent.fromJson', () { - final json = mockSuggestedContent.toJson(); - final feedItem = FeedItem.fromJson(json); - expect(feedItem, isA()); - expect(feedItem, equals(mockSuggestedContent)); - }); - test('dispatches to EngagementContent.fromJson', () { final json = mockEngagementContent.toJson(); final feedItem = FeedItem.fromJson(json); @@ -163,12 +149,6 @@ void main() { expect(deserialized.toJson(), equals(json)); }); - test('serializes SuggestedContent correctly', () { - final json = mockSuggestedContent.toJson(); - final deserialized = FeedItem.fromJson(json); - expect(deserialized.toJson(), equals(json)); - }); - test('serializes EngagementContent correctly', () { final json = mockEngagementContent.toJson(); final deserialized = FeedItem.fromJson(json); diff --git a/test/src/models/feed_decorators/ad_placement_test.dart b/test/src/models/feed_decorators/ad_placement_test.dart index 18706576..59b4190b 100644 --- a/test/src/models/feed_decorators/ad_placement_test.dart +++ b/test/src/models/feed_decorators/ad_placement_test.dart @@ -1,4 +1,4 @@ -import 'package:ht_shared/src/models/feed_decorators/ad_placement.dart'; +import 'package:ht_shared/src/models/feed_decorators/ad.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/src/models/feed_decorators/ad_test.dart b/test/src/models/feed_decorators/ad_test.dart index 254465ec..41015c5a 100644 --- a/test/src/models/feed_decorators/ad_test.dart +++ b/test/src/models/feed_decorators/ad_test.dart @@ -1,7 +1,6 @@ import 'package:ht_shared/src/models/core/content_type.dart'; import 'package:ht_shared/src/models/core/feed_item_action.dart'; import 'package:ht_shared/src/models/feed_decorators/ad.dart'; -import 'package:ht_shared/src/models/feed_decorators/ad_placement.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:test/test.dart'; import 'package:uuid/uuid.dart'; diff --git a/test/src/models/feed_decorators/engagement_content_test.dart b/test/src/models/feed_decorators/engagement_content_test.dart index 2c07babd..4fe1ba62 100644 --- a/test/src/models/feed_decorators/engagement_content_test.dart +++ b/test/src/models/feed_decorators/engagement_content_test.dart @@ -76,7 +76,8 @@ void main() { id: testId, title: 'Title', description: 'Description', - engagementContentType: EngagementContentType.feedback, + engagementContentType: + EngagementContentType.rateApp, // Changed from feedback callToActionText: 'Click Me', callToActionUrl: 'https://cta.com', ); @@ -87,7 +88,7 @@ void main() { testId, 'Title', 'Description', - EngagementContentType.feedback, + EngagementContentType.rateApp, // Changed from feedback 'Click Me', 'https://cta.com', 'engagement_content', @@ -132,7 +133,7 @@ void main() { final json = { 'id': testId, 'title': 'Simple Title', - 'engagement_content_type': 'feedback', + 'engagement_content_type': 'rate_app', // Changed from feedback 'type': 'engagement_content', 'action': { 'type': 'open_external_url', @@ -145,7 +146,8 @@ void main() { expect(instance.id, testId); expect(instance.title, 'Simple Title'); expect(instance.description, isNull); - expect(instance.engagementContentType, EngagementContentType.feedback); + expect(instance.engagementContentType, + EngagementContentType.rateApp); // Changed from feedback expect(instance.callToActionText, isNull); expect(instance.callToActionUrl, isNull); }); @@ -200,7 +202,8 @@ void main() { final instance = createSubject( id: testId, title: 'Simple Title', - engagementContentType: EngagementContentType.feedback, + engagementContentType: + EngagementContentType.rateApp, // Changed from feedback ); final json = instance.toJson(); @@ -208,7 +211,7 @@ void main() { expect(json, { 'id': testId, 'title': 'Simple Title', - 'engagement_content_type': 'feedback', + 'engagement_content_type': 'rate_app', // Changed from feedback 'type': 'engagement_content', 'action': { 'type': 'open_external_url', diff --git a/test/src/models/feed_decorators/engagement_content_type_test.dart b/test/src/models/feed_decorators/engagement_content_type_test.dart index 7c8144d0..6775ceaa 100644 --- a/test/src/models/feed_decorators/engagement_content_type_test.dart +++ b/test/src/models/feed_decorators/engagement_content_type_test.dart @@ -9,11 +9,7 @@ void main() { containsAll([ EngagementContentType.signUp, EngagementContentType.upgrade, - EngagementContentType.feedback, - EngagementContentType.survey, EngagementContentType.rateApp, - EngagementContentType.shareApp, - EngagementContentType.custom, ]), ); }); @@ -21,11 +17,7 @@ void main() { test('string representation (name) matches expected camelCase', () { expect(EngagementContentType.signUp.name, 'signUp'); expect(EngagementContentType.upgrade.name, 'upgrade'); - expect(EngagementContentType.feedback.name, 'feedback'); - expect(EngagementContentType.survey.name, 'survey'); expect(EngagementContentType.rateApp.name, 'rateApp'); - expect(EngagementContentType.shareApp.name, 'shareApp'); - expect(EngagementContentType.custom.name, 'custom'); }); test('can be created from string using values.byName', () { @@ -37,26 +29,10 @@ void main() { EngagementContentType.values.byName('upgrade'), EngagementContentType.upgrade, ); - expect( - EngagementContentType.values.byName('feedback'), - EngagementContentType.feedback, - ); - expect( - EngagementContentType.values.byName('survey'), - EngagementContentType.survey, - ); expect( EngagementContentType.values.byName('rateApp'), EngagementContentType.rateApp, ); - expect( - EngagementContentType.values.byName('shareApp'), - EngagementContentType.shareApp, - ); - expect( - EngagementContentType.values.byName('custom'), - EngagementContentType.custom, - ); }); test('values.byName throws ArgumentError for unknown value', () { diff --git a/test/src/models/feed_decorators/suggested_content_display_type_test.dart b/test/src/models/feed_decorators/suggested_content_display_type_test.dart deleted file mode 100644 index 80958aa1..00000000 --- a/test/src/models/feed_decorators/suggested_content_display_type_test.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:ht_shared/src/models/feed_decorators/suggested_content_display_type.dart'; -import 'package:test/test.dart'; - -void main() { - group('SuggestedContentDisplayType', () { - test('has correct string values for JSON serialization', () { - expect( - SuggestedContentDisplayType.horizontalCardList.name, - 'horizontalCardList', - ); - expect( - SuggestedContentDisplayType.verticalCardList.name, - 'verticalCardList', - ); - expect(SuggestedContentDisplayType.grid.name, 'grid'); - expect( - SuggestedContentDisplayType.singlePromotionalCard.name, - 'singlePromotionalCard', - ); - expect(SuggestedContentDisplayType.textList.name, 'textList'); - }); - - test('can be created from string values', () { - expect( - SuggestedContentDisplayType.values.byName('horizontalCardList'), - SuggestedContentDisplayType.horizontalCardList, - ); - expect( - SuggestedContentDisplayType.values.byName('verticalCardList'), - SuggestedContentDisplayType.verticalCardList, - ); - expect( - SuggestedContentDisplayType.values.byName('grid'), - SuggestedContentDisplayType.grid, - ); - expect( - SuggestedContentDisplayType.values.byName('singlePromotionalCard'), - SuggestedContentDisplayType.singlePromotionalCard, - ); - expect( - SuggestedContentDisplayType.values.byName('textList'), - SuggestedContentDisplayType.textList, - ); - }); - - test('has correct toString representation', () { - expect( - SuggestedContentDisplayType.horizontalCardList.toString(), - 'SuggestedContentDisplayType.horizontalCardList', - ); - expect( - SuggestedContentDisplayType.verticalCardList.toString(), - 'SuggestedContentDisplayType.verticalCardList', - ); - expect( - SuggestedContentDisplayType.grid.toString(), - 'SuggestedContentDisplayType.grid', - ); - expect( - SuggestedContentDisplayType.singlePromotionalCard.toString(), - 'SuggestedContentDisplayType.singlePromotionalCard', - ); - expect( - SuggestedContentDisplayType.textList.toString(), - 'SuggestedContentDisplayType.textList', - ); - }); - }); -} diff --git a/test/src/models/feed_decorators/suggested_content_test.dart b/test/src/models/feed_decorators/suggested_content_test.dart deleted file mode 100644 index afef9644..00000000 --- a/test/src/models/feed_decorators/suggested_content_test.dart +++ /dev/null @@ -1,255 +0,0 @@ -import 'package:ht_shared/src/models/core/content_type.dart'; -import 'package:ht_shared/src/models/core/feed_item.dart'; -import 'package:ht_shared/src/models/core/feed_item_action.dart'; -import 'package:ht_shared/src/models/entities/category.dart'; -import 'package:ht_shared/src/models/entities/country.dart'; -import 'package:ht_shared/src/models/entities/headline.dart'; -import 'package:ht_shared/src/models/entities/source.dart'; -import 'package:ht_shared/src/models/feed_decorators/suggested_content.dart'; -import 'package:ht_shared/src/models/feed_decorators/suggested_content_display_type.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:test/test.dart'; -import 'package:uuid/uuid.dart'; - -void main() { - group('SuggestedContent', () { - const testId = 'test-suggested-id'; - const testTitle = 'Suggested for You'; - const testDescription = 'Based on your reading history.'; - const testDisplayType = SuggestedContentDisplayType.horizontalCardList; - const defaultAction = OpenExternalUrl(url: 'http://default.com'); - - final mockHeadline = Headline( - id: 'headline-1', - title: 'Mock Headline', - action: defaultAction, - ); - final mockCategory = Category( - id: 'category-1', - name: 'Mock Category', - action: defaultAction, - ); - final mockSource = Source( - id: 'source-1', - name: 'Mock Source', - action: defaultAction, - ); - final mockCountry = Country( - id: 'country-1', - isoCode: 'US', - name: 'Mock Country', - flagUrl: 'http://mock.com/flag.png', - action: defaultAction, - ); - - final testItems = [ - mockHeadline, - mockCategory, - mockSource, - mockCountry, - ]; - - SuggestedContent createSubject({ - String? id, - String? title = testTitle, - String? description = testDescription, - SuggestedContentDisplayType displayType = testDisplayType, - List? items, - FeedItemAction action = defaultAction, - }) { - return SuggestedContent( - id: id, - title: title, - description: description, - displayType: displayType, - items: items ?? testItems, - action: action, - ); - } - - group('constructor', () { - test('generates id when not provided', () { - final content = createSubject(); - expect(content.id, isA()); - expect(Uuid.isValidUUID(fromString: content.id), isTrue); - }); - - test('uses provided id', () { - final content = createSubject(id: testId); - expect(content.id, testId); - }); - - test('initializes all properties correctly', () { - final content = createSubject(); - expect(content.title, testTitle); - expect(content.description, testDescription); - expect(content.displayType, testDisplayType); - expect(content.items, testItems); - expect(content.action, defaultAction); - expect(content.type, 'suggested_content'); - }); - - test('defaults items to empty list if not provided', () { - final content = SuggestedContent( - title: testTitle, - displayType: testDisplayType, - items: const [], - action: defaultAction, - ); - expect(content.items, isEmpty); - }); - }); - - group('copyWith', () { - test('returns a new instance with updated fields', () { - const newTitle = 'New Suggested Title'; - const newDisplayType = SuggestedContentDisplayType.verticalCardList; - final newItems = [mockHeadline]; - const newAction = OpenInternalContent( - contentId: 'new-suggested-content', - contentType: ContentType.source, - ); - - final originalContent = createSubject(); - final updatedContent = originalContent.copyWith( - title: newTitle, - displayType: newDisplayType, - items: newItems, - action: newAction, - ); - - expect(updatedContent.id, originalContent.id); - expect(updatedContent.title, newTitle); - expect(updatedContent.description, originalContent.description); - expect(updatedContent.displayType, newDisplayType); - expect(updatedContent.items, newItems); - expect(updatedContent.action, newAction); - expect(updatedContent.type, originalContent.type); - }); - - test('returns an identical copy if no updates provided', () { - final originalContent = createSubject(); - final copiedContent = originalContent.copyWith(); - expect(copiedContent, originalContent); - expect(identical(copiedContent, originalContent), isFalse); - }); - }); - - group('toJson', () { - test('serializes full SuggestedContent object to JSON', () { - final content = createSubject(); - final json = content.toJson(); - - expect(json, { - 'id': content.id, - 'title': testTitle, - 'description': testDescription, - 'display_type': 'horizontal_card_list', - 'items': testItems.map((e) => e.toJson()).toList(), - 'type': 'suggested_content', - 'action': defaultAction.toJson(), - }); - }); - - test('omits null optional fields from JSON', () { - final content = createSubject(title: null, description: null); - final json = content.toJson(); - - expect(json.containsKey('title'), isFalse); - expect(json.containsKey('description'), isFalse); - }); - }); - - group('fromJson', () { - test('deserializes full JSON to SuggestedContent object', () { - final json = { - 'id': testId, - 'title': testTitle, - 'description': testDescription, - 'display_type': 'horizontal_card_list', - 'items': testItems.map((e) => e.toJson()).toList(), - 'type': 'suggested_content', - 'action': defaultAction.toJson(), - }; - final content = SuggestedContent.fromJson(json); - - expect(content.id, testId); - expect(content.title, testTitle); - expect(content.description, testDescription); - expect(content.displayType, testDisplayType); - expect(content.items, testItems); - expect(content.action, defaultAction); - expect(content.type, 'suggested_content'); - }); - - test('deserializes JSON with missing optional fields', () { - final json = { - 'id': testId, - 'display_type': 'horizontal_card_list', - 'items': testItems.map((e) => e.toJson()).toList(), - 'type': 'suggested_content', - 'action': defaultAction.toJson(), - }; - final content = SuggestedContent.fromJson(json); - - expect(content.title, isNull); - expect(content.description, isNull); - }); - - test('deserializes JSON with unknown displayType gracefully', () { - final json = { - 'id': testId, - 'title': testTitle, - 'display_type': 'unknown_type', - 'items': testItems.map((e) => e.toJson()).toList(), - 'type': 'suggested_content', - 'action': defaultAction.toJson(), - }; - expect( - () => SuggestedContent.fromJson(json), - throwsA(isA()), - ); - }); - - test('deserializes JSON with empty items list', () { - final json = { - 'id': testId, - 'title': testTitle, - 'display_type': 'horizontal_card_list', - 'items': [], - 'type': 'suggested_content', - 'action': defaultAction.toJson(), - }; - final content = SuggestedContent.fromJson(json); - expect(content.items, isEmpty); - }); - }); - - group('Equatable', () { - test('instances with same properties are equal', () { - final content1 = createSubject(id: '1'); - final content2 = createSubject(id: '1'); - expect(content1, content2); - }); - - test('instances with different properties are not equal', () { - final content1 = createSubject(id: '1'); - final content2 = createSubject(id: '2'); - expect(content1, isNot(equals(content2))); - }); - - test('props list contains all relevant fields', () { - final content = createSubject(); - expect(content.props, [ - content.id, - content.title, - content.description, - content.displayType, - content.items, - content.type, - content.action, - ]); - }); - }); - }); -} diff --git a/test/src/models/feed_extras/engagement_content_template_test.dart b/test/src/models/feed_extras/engagement_content_template_test.dart deleted file mode 100644 index 437649b6..00000000 --- a/test/src/models/feed_extras/engagement_content_template_test.dart +++ /dev/null @@ -1,127 +0,0 @@ -import 'package:ht_shared/ht_shared.dart'; -import 'package:test/test.dart'; - -void main() { - group('EngagementContentTemplate', () { - EngagementContentTemplate createSubject({ - EngagementTemplateType type = EngagementTemplateType.rateApp, - String title = 'Default Title', // Made non-nullable with a default - String? description, - String? callToActionText, - }) { - return EngagementContentTemplate( - type: type, - title: title, // Now always non-nullable - description: description, - callToActionText: callToActionText, - ); - } - - test('can be instantiated', () { - final instance = createSubject(); - expect(instance, isNotNull); - expect(instance.type, EngagementTemplateType.rateApp); - }); - - test('supports value equality', () { - final instanceA = createSubject(title: 'Title A'); - final instanceB = createSubject(title: 'Title A'); - final instanceC = createSubject( - type: EngagementTemplateType.linkAccount, - title: 'Title C', - ); - - expect(instanceA, equals(instanceB)); - expect(instanceA, isNot(equals(instanceC))); - }); - - test('props are correct', () { - final instance = createSubject( - type: EngagementTemplateType.upgradeToPremium, - title: 'Upgrade Now', - description: 'Unlock premium features.', - callToActionText: 'Upgrade', - ); - - expect( - instance.props, - [ - EngagementTemplateType.upgradeToPremium, - 'Upgrade Now', - 'Unlock premium features.', - 'Upgrade', - ], - ); - }); - - group('fromJson', () { - test('returns correct EngagementContentTemplate object', () { - final json = { - 'type': 'rate_app', - 'title': 'Rate Our App', - 'description': 'Let us know what you think!', - 'call_to_action_text': 'Rate Now', - }; - - final instance = EngagementContentTemplate.fromJson(json); - - expect(instance.type, EngagementTemplateType.rateApp); - expect(instance.title, 'Rate Our App'); - expect(instance.description, 'Let us know what you think!'); - expect(instance.callToActionText, 'Rate Now'); - }); - - test('handles null optional fields', () { - final json = { - 'type': 'link_account', - 'title': 'Link Your Account Now', // Provide required title - }; - - final instance = EngagementContentTemplate.fromJson(json); - - expect(instance.type, EngagementTemplateType.linkAccount); - expect( - instance.title, - 'Link Your Account Now', - ); // Expect the provided title - expect(instance.description, isNull); - expect(instance.callToActionText, isNull); - }); - }); - - group('toJson', () { - test('returns correct JSON map', () { - final instance = createSubject( - type: EngagementTemplateType.completeProfile, - title: 'Complete Profile', - description: 'Add more details to your profile.', - callToActionText: 'Go to Profile', - ); - - final json = instance.toJson(); - - expect(json, { - 'type': 'complete_profile', - 'title': 'Complete Profile', - 'description': 'Add more details to your profile.', - 'call_to_action_text': 'Go to Profile', - }); - }); - - test('handles null optional fields', () { - final instance = createSubject( - type: EngagementTemplateType.exploreNewFeature, - title: 'New Feature!', - ); // description and callToActionText are null - - final json = instance.toJson(); - - expect(json, { - 'type': 'explore_new_feature', - 'title': 'New Feature!', - // Null fields should be omitted by includeIfNull: false - }); - }); - }); - }); -} diff --git a/test/src/models/feed_extras/feed_template_types_test.dart b/test/src/models/feed_extras/feed_template_types_test.dart deleted file mode 100644 index 54e4f73e..00000000 --- a/test/src/models/feed_extras/feed_template_types_test.dart +++ /dev/null @@ -1,107 +0,0 @@ -import 'package:ht_shared/ht_shared.dart'; -import 'package:test/test.dart'; - -void main() { - group('EngagementTemplateType', () { - test('has all expected values', () { - expect( - EngagementTemplateType.values, - containsAll([ - EngagementTemplateType.rateApp, - EngagementTemplateType.linkAccount, - EngagementTemplateType.upgradeToPremium, - EngagementTemplateType.completeProfile, - EngagementTemplateType.exploreNewFeature, - ]), - ); - }); - - test('string representation (name) matches expected camelCase', () { - expect(EngagementTemplateType.rateApp.name, 'rateApp'); - expect(EngagementTemplateType.linkAccount.name, 'linkAccount'); - expect(EngagementTemplateType.upgradeToPremium.name, 'upgradeToPremium'); - expect(EngagementTemplateType.completeProfile.name, 'completeProfile'); - expect( - EngagementTemplateType.exploreNewFeature.name, - 'exploreNewFeature', - ); - }); - - test('can be created from string using values.byName', () { - expect( - EngagementTemplateType.values.byName('rateApp'), - EngagementTemplateType.rateApp, - ); - expect( - EngagementTemplateType.values.byName('linkAccount'), - EngagementTemplateType.linkAccount, - ); - expect( - EngagementTemplateType.values.byName('upgradeToPremium'), - EngagementTemplateType.upgradeToPremium, - ); - expect( - EngagementTemplateType.values.byName('completeProfile'), - EngagementTemplateType.completeProfile, - ); - expect( - EngagementTemplateType.values.byName('exploreNewFeature'), - EngagementTemplateType.exploreNewFeature, - ); - }); - - test('values.byName throws ArgumentError for unknown value', () { - expect( - () => EngagementTemplateType.values.byName('unknownType'), - throwsA(isA()), - ); - }); - }); - - group('SuggestionTemplateType', () { - test('has all expected values', () { - expect( - SuggestionTemplateType.values, - containsAll([ - SuggestionTemplateType.categoriesToFollow, - SuggestionTemplateType.sourcesToFollow, - SuggestionTemplateType.countriesToFollow, - ]), - ); - }); - - test('string representation (name) matches expected camelCase', () { - expect( - SuggestionTemplateType.categoriesToFollow.name, - 'categoriesToFollow', - ); - expect(SuggestionTemplateType.sourcesToFollow.name, 'sourcesToFollow'); - expect( - SuggestionTemplateType.countriesToFollow.name, - 'countriesToFollow', - ); - }); - - test('can be created from string using values.byName', () { - expect( - SuggestionTemplateType.values.byName('categoriesToFollow'), - SuggestionTemplateType.categoriesToFollow, - ); - expect( - SuggestionTemplateType.values.byName('sourcesToFollow'), - SuggestionTemplateType.sourcesToFollow, - ); - expect( - SuggestionTemplateType.values.byName('countriesToFollow'), - SuggestionTemplateType.countriesToFollow, - ); - }); - - test('values.byName throws ArgumentError for unknown value', () { - expect( - () => SuggestionTemplateType.values.byName('unknownType'), - throwsA(isA()), - ); - }); - }); -} diff --git a/test/src/models/feed_extras/suggested_content_template_test.dart b/test/src/models/feed_extras/suggested_content_template_test.dart deleted file mode 100644 index 54ded275..00000000 --- a/test/src/models/feed_extras/suggested_content_template_test.dart +++ /dev/null @@ -1,171 +0,0 @@ -import 'package:ht_shared/ht_shared.dart'; -import 'package:test/test.dart'; - -void main() { - group('SuggestedContentTemplate', () { - SuggestedContentTemplate createSubject({ - SuggestionTemplateType type = SuggestionTemplateType.categoriesToFollow, - SuggestedContentDisplayType displayType = - SuggestedContentDisplayType.horizontalCardList, - ContentType suggestedContentType = ContentType.category, - String? title, - String? description, - int? maxItemsToDisplay, - String? fetchCriteria, - }) { - return SuggestedContentTemplate( - type: type, - displayType: displayType, - suggestedContentType: suggestedContentType, - title: title, - description: description, - maxItemsToDisplay: maxItemsToDisplay, - fetchCriteria: fetchCriteria, - ); - } - - test('can be instantiated', () { - final instance = createSubject(); - expect(instance, isNotNull); - expect(instance.type, SuggestionTemplateType.categoriesToFollow); - expect( - instance.displayType, - SuggestedContentDisplayType.horizontalCardList, - ); - expect(instance.suggestedContentType, ContentType.category); - }); - - test('supports value equality', () { - final instanceA = createSubject( - title: 'Title A', - ); - final instanceB = createSubject( - title: 'Title A', - ); - final instanceC = createSubject( - type: SuggestionTemplateType.sourcesToFollow, - displayType: SuggestedContentDisplayType.verticalCardList, - suggestedContentType: ContentType.source, - title: 'Title C', - ); - - expect(instanceA, equals(instanceB)); - expect(instanceA, isNot(equals(instanceC))); - }); - - test('props are correct', () { - final instance = createSubject( - type: SuggestionTemplateType.countriesToFollow, - displayType: SuggestedContentDisplayType.grid, - suggestedContentType: ContentType.country, - title: 'Explore Countries', - description: 'Find news from around the world', - maxItemsToDisplay: 10, - fetchCriteria: 'popular', - ); - - expect( - instance.props, - [ - SuggestionTemplateType.countriesToFollow, - 'Explore Countries', - 'Find news from around the world', - SuggestedContentDisplayType.grid, - ContentType.country, - 10, - 'popular', - ], - ); - }); - - group('fromJson', () { - test('returns correct SuggestedContentTemplate object', () { - final json = { - 'type': 'categories_to_follow', - 'title': 'Suggested Categories', - 'description': 'Discover new topics.', - 'display_type': 'horizontal_card_list', - 'suggested_content_type': 'category', - 'max_items_to_display': 5, - 'fetch_criteria': 'trending', - }; - - final instance = SuggestedContentTemplate.fromJson(json); - - expect(instance.type, SuggestionTemplateType.categoriesToFollow); - expect(instance.title, 'Suggested Categories'); - expect(instance.description, 'Discover new topics.'); - expect( - instance.displayType, - SuggestedContentDisplayType.horizontalCardList, - ); - expect(instance.suggestedContentType, ContentType.category); - expect(instance.maxItemsToDisplay, 5); - expect(instance.fetchCriteria, 'trending'); - }); - - test('handles null optional fields', () { - final json = { - 'type': 'sources_to_follow', - 'display_type': 'vertical_card_list', - 'suggested_content_type': 'source', - }; - - final instance = SuggestedContentTemplate.fromJson(json); - - expect(instance.type, SuggestionTemplateType.sourcesToFollow); - expect(instance.title, isNull); - expect(instance.description, isNull); - expect( - instance.displayType, - SuggestedContentDisplayType.verticalCardList, - ); - expect(instance.suggestedContentType, ContentType.source); - expect(instance.maxItemsToDisplay, isNull); - expect(instance.fetchCriteria, isNull); - }); - }); - - group('toJson', () { - test('returns correct JSON map', () { - final instance = createSubject( - type: SuggestionTemplateType.countriesToFollow, - displayType: SuggestedContentDisplayType.grid, - suggestedContentType: ContentType.country, - title: 'Explore Countries', - description: 'Find news from around the world', - maxItemsToDisplay: 10, - fetchCriteria: 'popular', - ); - - final json = instance.toJson(); - - expect(json, { - 'type': 'countries_to_follow', - 'title': 'Explore Countries', - 'description': 'Find news from around the world', - 'display_type': 'grid', - 'suggested_content_type': 'country', - 'max_items_to_display': 10, - 'fetch_criteria': 'popular', - }); - }); - - test('handles null optional fields', () { - final instance = createSubject( - type: SuggestionTemplateType.sourcesToFollow, - displayType: SuggestedContentDisplayType.textList, - suggestedContentType: ContentType.source, - ); - - final json = instance.toJson(); - - expect(json, { - 'type': 'sources_to_follow', - 'display_type': 'text_list', - 'suggested_content_type': 'source', - }); - }); - }); - }); -} diff --git a/test/src/models/remote_config/app_config_test.dart b/test/src/models/remote_config/app_config_test.dart index 7ed1d0ca..7b370a24 100644 --- a/test/src/models/remote_config/app_config_test.dart +++ b/test/src/models/remote_config/app_config_test.dart @@ -5,7 +5,7 @@ import 'package:test/test.dart'; void main() { group('AppConfig', () { - const mockUserPreferenceLimits = UserPreferenceLimits( + const mockUserPreferenceConfig = UserPreferenceConfig( guestFollowedItemsLimit: 5, guestSavedHeadlinesLimit: 10, authenticatedFollowedItemsLimit: 15, @@ -16,14 +16,14 @@ void main() { const appConfig = AppConfig( id: 'app_config', - userPreferenceLimits: mockUserPreferenceLimits, + userPreferenceLimits: mockUserPreferenceConfig, ); group('constructor', () { test('returns correct instance', () { expect(appConfig, isA()); expect(appConfig.id, 'app_config'); - expect(appConfig.userPreferenceLimits, mockUserPreferenceLimits); + expect(appConfig.userPreferenceLimits, mockUserPreferenceConfig); }); test('defaults userPreferenceLimits when not provided', () { @@ -77,7 +77,7 @@ void main() { // We need to compare with an AppConfig that also has the default adConfig const expectedAppConfig = AppConfig( id: 'app_config', - userPreferenceLimits: mockUserPreferenceLimits, + userPreferenceLimits: mockUserPreferenceConfig, // adConfig will take its default from the AppConfig constructor ); expect(result, expectedAppConfig); @@ -140,11 +140,11 @@ void main() { test('instances with the same properties are equal', () { const config1 = AppConfig( id: 'config-1', - userPreferenceLimits: mockUserPreferenceLimits, + userPreferenceLimits: mockUserPreferenceConfig, ); const config2 = AppConfig( id: 'config-1', - userPreferenceLimits: mockUserPreferenceLimits, + userPreferenceLimits: mockUserPreferenceConfig, ); expect(config1, config2); }); @@ -152,9 +152,9 @@ void main() { test('instances with different properties are not equal', () { const config1 = AppConfig( id: 'config-2', - userPreferenceLimits: mockUserPreferenceLimits, + userPreferenceLimits: mockUserPreferenceConfig, ); - const differentLimits = UserPreferenceLimits( + const differentLimits = UserPreferenceConfig( guestFollowedItemsLimit: 6, // Different limit guestSavedHeadlinesLimit: 10, authenticatedFollowedItemsLimit: 15, diff --git a/test/src/models/remote_config/feed_rules_test.dart b/test/src/models/remote_config/feed_rules_test.dart deleted file mode 100644 index a69e4516..00000000 --- a/test/src/models/remote_config/feed_rules_test.dart +++ /dev/null @@ -1,388 +0,0 @@ -import 'package:ht_shared/ht_shared.dart'; -import 'package:test/test.dart'; - -void main() { - group('PlacementCriteria', () { - PlacementCriteria createSubject({ - int? afterPrimaryItemIndex, - String? relativePosition, - int? minPrimaryItemsRequired, - }) { - return PlacementCriteria( - afterPrimaryItemIndex: afterPrimaryItemIndex, - relativePosition: relativePosition, - minPrimaryItemsRequired: minPrimaryItemsRequired, - ); - } - - test('can be instantiated', () { - final instance = createSubject(); - expect(instance, isNotNull); - expect(instance.afterPrimaryItemIndex, isNull); - expect(instance.relativePosition, isNull); - expect(instance.minPrimaryItemsRequired, isNull); - }); - - test('supports value equality', () { - final instanceA = createSubject( - afterPrimaryItemIndex: 5, - relativePosition: 'middle', - minPrimaryItemsRequired: 10, - ); - final instanceB = createSubject( - afterPrimaryItemIndex: 5, - relativePosition: 'middle', - minPrimaryItemsRequired: 10, - ); - final instanceC = createSubject( - afterPrimaryItemIndex: 10, - ); - - expect(instanceA, equals(instanceB)); - expect(instanceA, isNot(equals(instanceC))); - }); - - test('props are correct', () { - final instance = createSubject( - afterPrimaryItemIndex: 5, - relativePosition: 'middle', - minPrimaryItemsRequired: 10, - ); - - expect( - instance.props, - [5, 'middle', 10], - ); - }); - - group('fromJson', () { - test('returns correct PlacementCriteria object', () { - final json = { - 'after_primary_item_index': 5, - 'relative_position': 'middle', - 'min_primary_items_required': 10, - }; - - final instance = PlacementCriteria.fromJson(json); - - expect(instance.afterPrimaryItemIndex, 5); - expect(instance.relativePosition, 'middle'); - expect(instance.minPrimaryItemsRequired, 10); - }); - - test('handles null optional fields', () { - final json = {}; - - final instance = PlacementCriteria.fromJson(json); - - expect(instance.afterPrimaryItemIndex, isNull); - expect(instance.relativePosition, isNull); - expect(instance.minPrimaryItemsRequired, isNull); - }); - }); - - group('toJson', () { - test('returns correct JSON map', () { - final instance = createSubject( - afterPrimaryItemIndex: 5, - relativePosition: 'middle', - minPrimaryItemsRequired: 10, - ); - - final json = instance.toJson(); - - expect(json, { - 'after_primary_item_index': 5, - 'relative_position': 'middle', - 'min_primary_items_required': 10, - }); - }); - - test('handles null optional fields', () { - final instance = createSubject(); - - final json = instance.toJson(); - - expect(json, {}); - }); - }); - }); - - group('EngagementRule', () { - EngagementRule createSubject({ - EngagementTemplateType templateType = EngagementTemplateType.rateApp, - List userRoles = const [UserRole.standardUser], - int? minDaysSinceAccountCreation, - int? maxTimesToShow, - int? minDaysSinceLastShown, - PlacementCriteria? placement, - }) { - return EngagementRule( - templateType: templateType, - userRoles: userRoles, - minDaysSinceAccountCreation: minDaysSinceAccountCreation, - maxTimesToShow: maxTimesToShow, - minDaysSinceLastShown: minDaysSinceLastShown, - placement: placement, - ); - } - - test('can be instantiated', () { - final instance = createSubject(); - expect(instance, isNotNull); - expect(instance.templateType, EngagementTemplateType.rateApp); - expect(instance.userRoles, [UserRole.standardUser]); - }); - - test('supports value equality', () { - final instanceA = createSubject( - userRoles: [UserRole.standardUser], - minDaysSinceAccountCreation: 7, - ); - final instanceB = createSubject( - userRoles: [UserRole.standardUser], - minDaysSinceAccountCreation: 7, - ); - final instanceC = createSubject( - templateType: EngagementTemplateType.linkAccount, - ); - - expect(instanceA, equals(instanceB)); - expect(instanceA, isNot(equals(instanceC))); - }); - - test('props are correct', () { - const placement = PlacementCriteria(afterPrimaryItemIndex: 3); - final instance = createSubject( - templateType: EngagementTemplateType.upgradeToPremium, - userRoles: [UserRole.premiumUser, UserRole.admin], - minDaysSinceAccountCreation: 30, - maxTimesToShow: 5, - minDaysSinceLastShown: 1, - placement: placement, - ); - - expect( - instance.props, - [ - EngagementTemplateType.upgradeToPremium, - [UserRole.premiumUser, UserRole.admin], - 30, - 5, - 1, - placement, - ], - ); - }); - - group('fromJson', () { - test('returns correct EngagementRule object', () { - final json = { - 'template_type': 'rate_app', - 'user_roles': ['standard_user', 'guest_user'], - 'min_days_since_account_creation': 7, - 'max_times_to_show': 3, - 'min_days_since_last_shown': 1, - 'placement': { - 'after_primary_item_index': 2, - }, - }; - - final instance = EngagementRule.fromJson(json); - - expect(instance.templateType, EngagementTemplateType.rateApp); - expect( - instance.userRoles, - [UserRole.standardUser, UserRole.guestUser], - ); - expect(instance.minDaysSinceAccountCreation, 7); - expect(instance.maxTimesToShow, 3); - expect(instance.minDaysSinceLastShown, 1); - expect(instance.placement, isA()); - expect(instance.placement!.afterPrimaryItemIndex, 2); - }); - - test('handles null optional fields', () { - final json = { - 'template_type': 'link_account', - 'user_roles': ['guest_user'], - }; - - final instance = EngagementRule.fromJson(json); - - expect(instance.templateType, EngagementTemplateType.linkAccount); - expect(instance.userRoles, [UserRole.guestUser]); - expect(instance.minDaysSinceAccountCreation, isNull); - expect(instance.maxTimesToShow, isNull); - expect(instance.minDaysSinceLastShown, isNull); - expect(instance.placement, isNull); - }); - }); - - group('toJson', () { - test('returns correct JSON map', () { - const placement = PlacementCriteria(relativePosition: 'end'); - final instance = createSubject( - templateType: EngagementTemplateType.completeProfile, - userRoles: [UserRole.admin], - minDaysSinceAccountCreation: 90, - maxTimesToShow: 1, - minDaysSinceLastShown: 30, - placement: placement, - ); - - final json = instance.toJson(); - - expect(json, { - 'template_type': 'complete_profile', - 'user_roles': ['admin'], - 'min_days_since_account_creation': 90, - 'max_times_to_show': 1, - 'min_days_since_last_shown': 30, - 'placement': { - 'relative_position': 'end', - }, - }); - }); - - test('handles null optional fields', () { - final instance = createSubject( - templateType: EngagementTemplateType.exploreNewFeature, - userRoles: [UserRole.standardUser], - ); - - final json = instance.toJson(); - - expect(json, { - 'template_type': 'explore_new_feature', - 'user_roles': ['standard_user'], - }); - }); - }); - }); - - group('SuggestionRule', () { - SuggestionRule createSubject({ - SuggestionTemplateType templateType = - SuggestionTemplateType.categoriesToFollow, - List userRoles = const [UserRole.standardUser], - PlacementCriteria? placement, - }) { - return SuggestionRule( - templateType: templateType, - userRoles: userRoles, - placement: placement, - ); - } - - test('can be instantiated', () { - final instance = createSubject(); - expect(instance, isNotNull); - expect(instance.templateType, SuggestionTemplateType.categoriesToFollow); - expect(instance.userRoles, [UserRole.standardUser]); - }); - - test('supports value equality', () { - final instanceA = createSubject( - userRoles: [UserRole.standardUser], - ); - final instanceB = createSubject( - userRoles: [UserRole.standardUser], - ); - final instanceC = createSubject( - templateType: SuggestionTemplateType.sourcesToFollow, - ); - - expect(instanceA, equals(instanceB)); - expect(instanceA, isNot(equals(instanceC))); - }); - - test('props are correct', () { - const placement = PlacementCriteria(minPrimaryItemsRequired: 5); - final instance = createSubject( - templateType: SuggestionTemplateType.countriesToFollow, - userRoles: [UserRole.guestUser, UserRole.premiumUser], - placement: placement, - ); - - expect( - instance.props, - [ - SuggestionTemplateType.countriesToFollow, - [UserRole.guestUser, UserRole.premiumUser], - placement, - ], - ); - }); - - group('fromJson', () { - test('returns correct SuggestionRule object', () { - final json = { - 'template_type': 'sources_to_follow', - 'user_roles': ['standard_user'], - 'placement': { - 'relative_position': 'start', - }, - }; - - final instance = SuggestionRule.fromJson(json); - - expect(instance.templateType, SuggestionTemplateType.sourcesToFollow); - expect(instance.userRoles, [UserRole.standardUser]); - expect(instance.placement, isA()); - expect(instance.placement!.relativePosition, 'start'); - }); - - test('handles null optional fields', () { - final json = { - 'template_type': 'categories_to_follow', - 'user_roles': ['guest_user'], - }; - - final instance = SuggestionRule.fromJson(json); - - expect( - instance.templateType, - SuggestionTemplateType.categoriesToFollow, - ); - expect(instance.userRoles, [UserRole.guestUser]); - expect(instance.placement, isNull); - }); - }); - - group('toJson', () { - test('returns correct JSON map', () { - const placement = PlacementCriteria(afterPrimaryItemIndex: 1); - final instance = createSubject( - templateType: SuggestionTemplateType.countriesToFollow, - userRoles: [UserRole.premiumUser], - placement: placement, - ); - - final json = instance.toJson(); - - expect(json, { - 'template_type': 'countries_to_follow', - 'user_roles': ['premium_user'], - 'placement': { - 'after_primary_item_index': 1, - }, - }); - }); - - test('handles null optional fields', () { - final instance = createSubject( - templateType: SuggestionTemplateType.sourcesToFollow, - userRoles: [UserRole.standardUser], - ); - - final json = instance.toJson(); - - expect(json, { - 'template_type': 'sources_to_follow', - 'user_roles': ['standard_user'], - }); - }); - }); - }); -} diff --git a/test/src/models/remote_config/user_preference_limits_test.dart b/test/src/models/remote_config/user_preference_config_test.dart similarity index 73% rename from test/src/models/remote_config/user_preference_limits_test.dart rename to test/src/models/remote_config/user_preference_config_test.dart index 62bdcdb3..51eba6af 100644 --- a/test/src/models/remote_config/user_preference_limits_test.dart +++ b/test/src/models/remote_config/user_preference_config_test.dart @@ -2,8 +2,8 @@ import 'package:ht_shared/ht_shared.dart'; // Import the barrel file import 'package:test/test.dart'; void main() { - group('UserPreferenceLimits', () { - const userPreferenceLimits = UserPreferenceLimits( + group('UserPreferenceConfig', () { + const userPreferenceConfig = UserPreferenceConfig( guestFollowedItemsLimit: 5, guestSavedHeadlinesLimit: 10, authenticatedFollowedItemsLimit: 15, @@ -14,13 +14,13 @@ void main() { group('constructor', () { test('returns correct instance', () { - expect(userPreferenceLimits, isA()); - expect(userPreferenceLimits.guestFollowedItemsLimit, 5); - expect(userPreferenceLimits.guestSavedHeadlinesLimit, 10); - expect(userPreferenceLimits.authenticatedFollowedItemsLimit, 15); - expect(userPreferenceLimits.authenticatedSavedHeadlinesLimit, 30); - expect(userPreferenceLimits.premiumFollowedItemsLimit, 30); - expect(userPreferenceLimits.premiumSavedHeadlinesLimit, 100); + expect(userPreferenceConfig, isA()); + expect(userPreferenceConfig.guestFollowedItemsLimit, 5); + expect(userPreferenceConfig.guestSavedHeadlinesLimit, 10); + expect(userPreferenceConfig.authenticatedFollowedItemsLimit, 15); + expect(userPreferenceConfig.authenticatedSavedHeadlinesLimit, 30); + expect(userPreferenceConfig.premiumFollowedItemsLimit, 30); + expect(userPreferenceConfig.premiumSavedHeadlinesLimit, 100); }); }); @@ -35,15 +35,15 @@ void main() { 'premium_saved_headlines_limit': 100, }; - final result = UserPreferenceLimits.fromJson(json); + final result = UserPreferenceConfig.fromJson(json); - expect(result, userPreferenceLimits); + expect(result, userPreferenceConfig); }); }); group('toJson', () { test('returns correct JSON map', () { - final json = userPreferenceLimits.toJson(); + final json = userPreferenceConfig.toJson(); expect(json['guest_followed_items_limit'], 5); expect(json['guest_saved_headlines_limit'], 10); @@ -56,7 +56,7 @@ void main() { group('Equatable', () { test('instances with the same properties are equal', () { - const limits1 = UserPreferenceLimits( + const config1 = UserPreferenceConfig( guestFollowedItemsLimit: 5, guestSavedHeadlinesLimit: 10, authenticatedFollowedItemsLimit: 15, @@ -64,7 +64,7 @@ void main() { premiumFollowedItemsLimit: 30, premiumSavedHeadlinesLimit: 100, ); - const limits2 = UserPreferenceLimits( + const config2 = UserPreferenceConfig( guestFollowedItemsLimit: 5, guestSavedHeadlinesLimit: 10, authenticatedFollowedItemsLimit: 15, @@ -72,11 +72,11 @@ void main() { premiumFollowedItemsLimit: 30, premiumSavedHeadlinesLimit: 100, ); - expect(limits1, limits2); + expect(config1, config2); }); test('instances with different properties are not equal', () { - const limits1 = UserPreferenceLimits( + const config1 = UserPreferenceConfig( guestFollowedItemsLimit: 5, guestSavedHeadlinesLimit: 10, authenticatedFollowedItemsLimit: 15, @@ -84,7 +84,7 @@ void main() { premiumFollowedItemsLimit: 30, premiumSavedHeadlinesLimit: 100, ); - const limits2 = UserPreferenceLimits( + const config2 = UserPreferenceConfig( guestFollowedItemsLimit: 6, // Different limit guestSavedHeadlinesLimit: 10, authenticatedFollowedItemsLimit: 15, @@ -92,7 +92,7 @@ void main() { premiumFollowedItemsLimit: 30, premiumSavedHeadlinesLimit: 100, ); - expect(limits1, isNot(equals(limits2))); + expect(config1, isNot(equals(config2))); }); }); });