Skip to content

Commit 2bfd49c

Browse files
committed
Keep unlimited text field lines and empty text variables through JSON
TextFieldProperties.maxLines null (unlimited) was left out of toJson and read back as the default of 1, so minLines 3 with unlimited max lines asserted at runtime. An explicit null is now written and read back as null; designs without the key keep reading as 1. A text variable holding '' returned null from getValue(), so conditions like x != '' were true. It now returns ''. A null value is stored as '' rather than the string 'null'. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VtH72Dg3rGsqwaEe62Sha4
1 parent 06c0240 commit 2bfd49c

6 files changed

Lines changed: 132 additions & 5 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
## Unreleased
22

3+
- `TextFieldProperties.maxLines`: `null` (unlimited lines) is written as an
4+
explicit `null` and reads back as `null`; a missing key still reads as 1.
5+
- `VariableData.getValue()` returns an empty text variable as `''` instead of
6+
`null`, and a `null` value is stored as `''` instead of `'null'`.
37
- `AuthOperation.sendEmailVerification`, `updatePassword` and
48
`deleteAccount`.
59
- `DelayAction` (`ActionType.delay`): waits `milliseconds` before the next

‎lib/src/api/models/variables_model.dart‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ class VariableData
121121
Object? value = '',
122122
this.type = VariableType.text,
123123
Map<String, dynamic>? extra,
124-
}) : value = sanitizeValueForVariableType(value, type).toString(),
124+
}) : value = sanitizeValueForVariableType(value, type) ?? '',
125125
extra = extra ?? {};
126126

127127
/// Duplicate a [VariableData] with the given parameters.
@@ -171,8 +171,11 @@ class VariableData
171171
);
172172

173173
/// Returns the value converted to the appropriate type according to [type].
174+
///
175+
/// A text variable returns its value as is, so one holding an empty string
176+
/// stays an empty string and compares equal to `''` in conditions.
174177
Object? getValue() => switch (type) {
175-
VariableType.text => value.isEmpty ? null : value,
178+
VariableType.text => value,
176179
VariableType.image => value.isEmpty ? null : value,
177180
VariableType.integer => num.tryParse(value).toInt(),
178181
VariableType.decimal => num.tryParse(value).toDouble(),

‎lib/src/api/nodes/text_field_node.dart‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,16 @@ class TextFieldProperties extends CustomProperties with FieldsHolder {
202202
int? maxLength;
203203

204204
/// Maximum lines of text that appear at a time before the field becomes
205-
/// scrollable.
205+
/// scrollable. Null means unlimited: the field grows with its content.
206+
///
207+
/// Stored JSON leaves the key out for the default of 1 and writes an
208+
/// explicit `null` for unlimited, so both survive a round trip. Designs
209+
/// saved before null was written have no key and keep reading as 1.
210+
@JsonKey(
211+
includeIfNull: true,
212+
readValue: _readMaxLines,
213+
fromJson: _maxLinesFromJson,
214+
)
206215
int? maxLines;
207216

208217
/// Maximum lines of text that appear at a time.
@@ -571,3 +580,18 @@ class TextFieldProperties extends CustomProperties with FieldsHolder {
571580
textInputAction,
572581
];
573582
}
583+
584+
/// Reads `maxLines`: a missing key is the default of 1, while an explicit
585+
/// `null` means unlimited lines.
586+
///
587+
/// An explicit null is passed on as [_unlimitedLines] because the generated
588+
/// reader replaces a null result with the constructor default of 1.
589+
Object? _readMaxLines(Map json, String key) =>
590+
json.containsKey(key) ? (json[key] ?? _unlimitedLines) : 1;
591+
592+
/// Converts what [_readMaxLines] returned into the field value.
593+
int? _maxLinesFromJson(Object? value) => value is num ? value.toInt() : null;
594+
595+
/// Marks an explicit `null` (unlimited lines) between [_readMaxLines] and
596+
/// [_maxLinesFromJson].
597+
const String _unlimitedLines = 'unlimited';

‎lib/src/api/nodes/text_field_node.g.dart‎

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import 'dart:convert';
2+
3+
import 'package:codelessly_api/codelessly_api.dart';
4+
import 'package:test/test.dart';
5+
6+
TextFieldProperties roundTrip(TextFieldProperties properties) =>
7+
TextFieldProperties.fromJson(jsonDecode(jsonEncode(properties.toJson())));
8+
9+
void main() {
10+
group('TextFieldProperties.maxLines', () {
11+
test('default of 1 is left out and reads back as 1', () {
12+
final properties = TextFieldProperties();
13+
expect(properties.toJson().containsKey('maxLines'), isFalse);
14+
expect(roundTrip(properties).maxLines, 1);
15+
});
16+
17+
test('null (unlimited) is written and survives a round trip', () {
18+
final properties = TextFieldProperties(maxLines: null, minLines: 3);
19+
final json = properties.toJson();
20+
expect(json.containsKey('maxLines'), isTrue);
21+
expect(json['maxLines'], isNull);
22+
23+
final decoded = roundTrip(properties);
24+
expect(decoded.maxLines, isNull);
25+
expect(decoded.minLines, 3);
26+
});
27+
28+
test('other values survive a round trip', () {
29+
expect(roundTrip(TextFieldProperties(maxLines: 5)).maxLines, 5);
30+
});
31+
32+
test('stored designs without the key keep reading as 1', () {
33+
expect(TextFieldProperties.fromJson({}).maxLines, 1);
34+
expect(TextFieldProperties.fromJson({'minLines': 3}).maxLines, 1);
35+
expect(TextFieldProperties.fromJson({'maxLines': 4.0}).maxLines, 4);
36+
});
37+
38+
test('an explicit null in stored JSON reads as unlimited', () {
39+
expect(TextFieldProperties.fromJson({'maxLines': null}).maxLines, isNull);
40+
});
41+
});
42+
}

‎test/variable_empty_text_test.dart‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import 'dart:convert';
2+
3+
import 'package:codelessly_api/codelessly_api.dart';
4+
import 'package:test/test.dart';
5+
6+
void main() {
7+
group('Empty text variables', () {
8+
test('a text variable created with "" keeps "" as its value', () {
9+
final variable = VariableData(id: 'a', name: 'x', value: '');
10+
expect(variable.value, '');
11+
expect(variable.getValue(), '');
12+
});
13+
14+
test('"" survives a JSON round trip', () {
15+
final variable = VariableData(id: 'a', name: 'x', value: '');
16+
final decoded = VariableData.fromJson(
17+
jsonDecode(jsonEncode(variable.toJson())),
18+
);
19+
expect(decoded.value, '');
20+
expect(decoded.getValue(), '');
21+
});
22+
23+
test('copyWith to "" keeps an empty string', () {
24+
final variable = VariableData(id: 'a', name: 'x', value: 'hi');
25+
expect(variable.copyWith(value: '').getValue(), '');
26+
});
27+
28+
test('a null value is stored as "" rather than "null"', () {
29+
final variable = VariableData(id: 'a', name: 'x', value: null);
30+
expect(variable.value, '');
31+
expect(variable.getValue(), '');
32+
final number = VariableData(
33+
id: 'b',
34+
name: 'n',
35+
value: 'abc',
36+
type: VariableType.integer,
37+
);
38+
expect(number.value, '');
39+
expect(number.getValue(), isNull);
40+
});
41+
42+
test('an empty image variable still has no value', () {
43+
final variable = VariableData(
44+
id: 'a',
45+
name: 'img',
46+
value: '',
47+
type: VariableType.image,
48+
);
49+
expect(variable.getValue(), isNull);
50+
});
51+
});
52+
}

0 commit comments

Comments
 (0)