diff --git a/CHANGELOG.md b/CHANGELOG.md index 42bd43f..dfe7a76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -108,6 +108,9 @@ ### Fixes +- Placeholders (variances, lists, grids, pages) read JSON that leaves out + empty `allowedTypes` or `deniedTypes`, as JSON written before this + release could. - `SpacerNode.fromJson` defaults `positioningMode` (align), `aspectRatioLock` (false) and `reactions` ([]) like every other node, and `GridViewNode.fromJson` defaults `isScrollable` (true) and diff --git a/lib/src/api/nodes/grid_view_node.g.dart b/lib/src/api/nodes/grid_view_node.g.dart index 5febad6..ab43ee6 100644 --- a/lib/src/api/nodes/grid_view_node.g.dart +++ b/lib/src/api/nodes/grid_view_node.g.dart @@ -259,14 +259,14 @@ GridViewNode _$GridViewNodeFromJson(Map json) => ..motion = json['motion'] == null ? null : MotionModel.fromJson(json['motion'] as Map) - ..deniedTypes = (json['deniedTypes'] as List) + ..deniedTypes = (readTypesOrEmpty(json, 'deniedTypes') as List) .map((e) => e as String) .toList() ..maxAllowedSize = json['maxAllowedSize'] == null ? null : SizeC.fromJson(json['maxAllowedSize'] as Map) ..ephemeral = json['ephemeral'] as bool? ?? false - ..allowedTypes = (json['allowedTypes'] as List) + ..allowedTypes = (readTypesOrEmpty(json, 'allowedTypes') as List) .map((e) => e as String) .toList() ..paging = diff --git a/lib/src/api/nodes/list_view_node.g.dart b/lib/src/api/nodes/list_view_node.g.dart index bb25f58..992d121 100644 --- a/lib/src/api/nodes/list_view_node.g.dart +++ b/lib/src/api/nodes/list_view_node.g.dart @@ -235,14 +235,14 @@ ListViewNode _$ListViewNodeFromJson(Map json) => ..motion = json['motion'] == null ? null : MotionModel.fromJson(json['motion'] as Map) - ..deniedTypes = (json['deniedTypes'] as List) + ..deniedTypes = (readTypesOrEmpty(json, 'deniedTypes') as List) .map((e) => e as String) .toList() ..maxAllowedSize = json['maxAllowedSize'] == null ? null : SizeC.fromJson(json['maxAllowedSize'] as Map) ..ephemeral = json['ephemeral'] as bool? ?? false - ..allowedTypes = (json['allowedTypes'] as List) + ..allowedTypes = (readTypesOrEmpty(json, 'allowedTypes') as List) .map((e) => e as String) .toList() ..isScrollable = json['isScrollable'] as bool diff --git a/lib/src/api/nodes/page_view_node.g.dart b/lib/src/api/nodes/page_view_node.g.dart index 541dfd3..f93ae6c 100644 --- a/lib/src/api/nodes/page_view_node.g.dart +++ b/lib/src/api/nodes/page_view_node.g.dart @@ -204,10 +204,10 @@ PageViewNode _$PageViewNodeFromJson(Map json) => ? null : MotionModel.fromJson(json['motion'] as Map) ..ephemeral = json['ephemeral'] as bool? ?? false - ..allowedTypes = (json['allowedTypes'] as List) + ..allowedTypes = (readTypesOrEmpty(json, 'allowedTypes') as List) .map((e) => e as String) .toList() - ..deniedTypes = (json['deniedTypes'] as List) + ..deniedTypes = (readTypesOrEmpty(json, 'deniedTypes') as List) .map((e) => e as String) .toList() ..maxAllowedSize = json['maxAllowedSize'] == null diff --git a/lib/src/api/nodes/single_placeholder_node.dart b/lib/src/api/nodes/single_placeholder_node.dart index 2cfa6ca..6bf5668 100644 --- a/lib/src/api/nodes/single_placeholder_node.dart +++ b/lib/src/api/nodes/single_placeholder_node.dart @@ -34,12 +34,14 @@ class SinglePlaceholderNode extends SceneNode /// The node types that are allowed to be inside this [SinglePlaceholderNode]. /// Example: 'APP_BAR' for AppBarNode. /// When empty, allow all types. + @JsonKey(readValue: readTypesOrEmpty) List allowedTypes; /// The node types that are not allowed to be inside this /// [SinglePlaceholderNode]. /// Example: "TEXT" for TextNode. /// When empty, allow all types. + @JsonKey(readValue: readTypesOrEmpty) List deniedTypes; /// If not null, this [SinglePlaceholderNode] will only accept nodes that do @@ -89,3 +91,7 @@ class SinglePlaceholderNode extends SceneNode _$SinglePlaceholderNodeToJson(this), ); } + +/// Reads [allowedTypes] or [deniedTypes] from [json], an empty list when the +/// key is missing: JSON that leaves empty lists out still reads. +Object? readTypesOrEmpty(Map json, String key) => json[key] ?? const []; diff --git a/lib/src/api/nodes/single_placeholder_node.g.dart b/lib/src/api/nodes/single_placeholder_node.g.dart index 72d475b..c3b2f15 100644 --- a/lib/src/api/nodes/single_placeholder_node.g.dart +++ b/lib/src/api/nodes/single_placeholder_node.g.dart @@ -57,10 +57,10 @@ Map _$SinglePlaceholderNodeOmitDefaults( SinglePlaceholderNode _$SinglePlaceholderNodeFromJson(Map json) => SinglePlaceholderNode( - allowedTypes: (json['allowedTypes'] as List) + allowedTypes: (readTypesOrEmpty(json, 'allowedTypes') as List) .map((e) => e as String) .toList(), - deniedTypes: (json['deniedTypes'] as List) + deniedTypes: (readTypesOrEmpty(json, 'deniedTypes') as List) .map((e) => e as String) .toList(), maxAllowedSize: json['maxAllowedSize'] == null diff --git a/lib/src/api/nodes/variance_node.g.dart b/lib/src/api/nodes/variance_node.g.dart index fcfd033..6f38375 100644 --- a/lib/src/api/nodes/variance_node.g.dart +++ b/lib/src/api/nodes/variance_node.g.dart @@ -177,10 +177,10 @@ VarianceNode _$VarianceNodeFromJson(Map json) => ? null : MotionModel.fromJson(json['motion'] as Map) ..ephemeral = json['ephemeral'] as bool? ?? false - ..allowedTypes = (json['allowedTypes'] as List) + ..allowedTypes = (readTypesOrEmpty(json, 'allowedTypes') as List) .map((e) => e as String) .toList() - ..deniedTypes = (json['deniedTypes'] as List) + ..deniedTypes = (readTypesOrEmpty(json, 'deniedTypes') as List) .map((e) => e as String) .toList() ..type = json['type'] as String; diff --git a/test/placeholder_types_missing_test.dart b/test/placeholder_types_missing_test.dart new file mode 100644 index 0000000..706697a --- /dev/null +++ b/test/placeholder_types_missing_test.dart @@ -0,0 +1,37 @@ +import 'package:codelessly_api/codelessly_api.dart'; +import 'package:test/test.dart'; + +void main() { + test('placeholders read JSON that leaves out empty type lists', () { + for (final String type in ['variance', 'listView', 'gridView']) { + final Map json = { + 'type': type, + 'id': 'x', + 'name': 'n', + 'basicBoxLocal': [0, 0, 100, 100], + 'children': [], + 'properties': {}, + if (type == 'variance') ...{ + 'variants': [ + {'id': 'default', 'name': 'Default', 'children': []}, + ], + 'currentVariantId': 'default', + }, + if (type == 'gridView') + 'properties': { + 'gridDelegate': { + 'type': 'fixedCrossAxisCount', + 'crossAxisCount': 2, + }, + }, + }; + json.remove('allowedTypes'); + json.remove('deniedTypes'); + final node = NodeJsonConverter().fromJson(json) as SinglePlaceholderNode; + expect(node.allowedTypes, isEmpty, reason: type); + // A list keeps its own default; a variance reads it as empty. + if (type == 'variance') expect(node.deniedTypes, isEmpty); + if (type == 'listView') expect(node.deniedTypes, ['listView']); + } + }); +}