Skip to content

Commit 5887abf

Browse files
committed
Merge branch 'pc_server'
2 parents bcbdc2c + 3b84021 commit 5887abf

File tree

7 files changed

+162
-7
lines changed

7 files changed

+162
-7
lines changed

.github/workflows/ci.yml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ jobs:
9797
- name: Flutter beta
9898
os: ubuntu-latest
9999
sdk: beta
100+
# WASM support
101+
- name: Flutter 3.38, Ubuntu, WASM
102+
os: ubuntu-latest
103+
sdk: 3.38.1
104+
wasm: true
100105
fail-fast: false
101106
name: Test ${{ matrix.name }}
102107
steps:
@@ -125,18 +130,22 @@ jobs:
125130
- name: Publish dry run
126131
run: cd packages/flutter && dart pub publish --dry-run
127132
- name: Run tests
133+
if: ${{ !matrix.wasm }}
128134
run: (cd packages/flutter && flutter test --coverage)
135+
- name: Run tests (WASM)
136+
if: ${{ matrix.wasm }}
137+
run: (cd packages/flutter && flutter test --platform chrome --wasm)
129138
- name: Convert code coverage
130139
# Needs to be adapted to collect the coverage at all platforms if platform specific code is added.
131-
if: ${{ always() && matrix.os == 'ubuntu-latest' }}
140+
if: ${{ always() && matrix.os == 'ubuntu-latest' && !matrix.wasm }}
132141
working-directory: packages/flutter
133142
run: |
134143
escapedPath="$(echo `pwd` | sed 's/\//\\\//g')"
135144
sed "s/^SF:lib/SF:$escapedPath\/lib/g" coverage/lcov.info > coverage/lcov-full.info
136145
- name: Upload code coverage
137146
uses: codecov/codecov-action@v5
138147
# Needs to be adapted to collect the coverage at all platforms if platform specific code is added.
139-
if: ${{ always() && matrix.os == 'ubuntu-latest' }}
148+
if: ${{ always() && matrix.os == 'ubuntu-latest' && !matrix.wasm }}
140149
with:
141150
files: packages/flutter/coverage/lcov-full.info
142151
fail_ci_if_error: false

.github/workflows/release-automated.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ env:
1414
package: ${{ startsWith(github.ref_name, 'dart-') && 'dart' || startsWith(github.ref_name, 'flutter-') && 'flutter' || '' }}
1515
jobs:
1616
release:
17-
if: github.event_name == 'push' && github.ref_type == 'branch'
17+
if: github.event_name == 'push' && github.ref_type == 'branch' && !startsWith(github.event.head_commit.message, 'chore(release)')
1818
runs-on: ubuntu-latest
1919
timeout-minutes: 10
2020
permissions:

packages/dart/CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
# [dart-v9.4.6](https://github.com/parse-community/Parse-SDK-Flutter/compare/dart-9.4.5...dart-9.4.6) (2025-12-04)
2+
3+
4+
### Bug Fixes
5+
6+
* TypeError on `addRelation` function ([#1098](https://github.com/parse-community/Parse-SDK-Flutter/issues/1098)) ([f284944](https://github.com/parse-community/Parse-SDK-Flutter/commit/f2849442f71ebf311bf10d01e967a7612ba66fe4))
7+
8+
# [dart-v9.4.5](https://github.com/parse-community/Parse-SDK-Flutter/compare/dart-9.4.4...dart-9.4.5) (2025-12-04)
9+
10+
11+
### Bug Fixes
12+
13+
* Incompatible `parseIsWeb` detection prevents WASM support ([#1096](https://github.com/parse-community/Parse-SDK-Flutter/issues/1096)) ([5b157b8](https://github.com/parse-community/Parse-SDK-Flutter/commit/5b157b897339634ecc2d0f66e3b3de612a243ea3))
14+
115
# [dart-v9.4.4](https://github.com/parse-community/Parse-SDK-Flutter/compare/dart-9.4.3...dart-9.4.4) (2025-12-04)
216

317

packages/dart/lib/src/base/parse_constants.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,4 @@ const String keyError = 'error';
8686
const String keyCode = 'code';
8787
const String keyNetworkError = 'NetworkError';
8888

89-
const bool parseIsWeb = identical(0, 0.0);
89+
const bool parseIsWeb = bool.fromEnvironment('dart.library.js_util');

packages/dart/lib/src/objects/parse_relation.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class _ParseRelation<T extends ParseObject>
6161

6262
// For offline caching, we keep track of every object
6363
// we've known to be in the relation.
64-
Set<T> knownObjects = <T>{};
64+
Set<ParseObject> knownObjects = <ParseObject>{};
6565

6666
_ParseRelationOperation? lastPreformedOperation;
6767

@@ -90,7 +90,7 @@ class _ParseRelation<T extends ParseObject>
9090

9191
lastPreformedOperation = relationOperation;
9292

93-
knownObjects = lastPreformedOperation!.value.toSet() as Set<T>;
93+
knownObjects = lastPreformedOperation!.value.toSet();
9494

9595
return this;
9696
}

packages/dart/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: parse_server_sdk
22
description: The Dart SDK to connect to Parse Server. Build your apps faster with Parse Platform, the complete application stack.
3-
version: 9.4.4
3+
version: 9.4.6
44
homepage: https://parseplatform.org
55
repository: https://github.com/parse-community/Parse-SDK-Flutter
66
issue_tracker: https://github.com/parse-community/Parse-SDK-Flutter/issues

packages/dart/test/src/objects/parse_object/parse_object_relation_test.dart

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,5 +624,137 @@ void main() {
624624
// assert
625625
expect(() => getRelation(), throwsA(isA<ParseRelationException>()));
626626
});
627+
628+
test('addRelation() should work with custom ParseObject subclasses', () {
629+
// arrange
630+
// Create custom ParseObject subclasses similar to the issue report
631+
final contact1 = Contact()..objectId = 'contact1';
632+
final contact2 = Contact()..objectId = 'contact2';
633+
634+
final order = Order();
635+
636+
// act & assert
637+
// This should not throw a TypeError
638+
expect(
639+
() => order.addRelation('receivers', [contact1, contact2]),
640+
returnsNormally,
641+
);
642+
643+
final toJsonAfterAddRelation = order.toJson(forApiRQ: true);
644+
645+
const expectedToJson = {
646+
"receivers": {
647+
"__op": "AddRelation",
648+
"objects": [
649+
{
650+
"__type": "Pointer",
651+
"className": "Contact",
652+
"objectId": "contact1",
653+
},
654+
{
655+
"__type": "Pointer",
656+
"className": "Contact",
657+
"objectId": "contact2",
658+
},
659+
],
660+
},
661+
};
662+
663+
expect(
664+
DeepCollectionEquality().equals(expectedToJson, toJsonAfterAddRelation),
665+
isTrue,
666+
);
667+
});
668+
669+
test(
670+
'addRelation() should work when getRelation<T>() was called first with typed generic',
671+
() {
672+
// This test reproduces issue #999
673+
// The issue occurs when:
674+
// 1. getRelation<Contact>() is called first (creating _ParseRelation<Contact>)
675+
// 2. Then addRelation() is called with Contact objects
676+
// 3. The merge operation creates a Set<ParseObject>
677+
// 4. Trying to cast Set<ParseObject> to Set<Contact> throws TypeError
678+
679+
// arrange
680+
final contact1 = Contact()..objectId = 'contact1';
681+
final contact2 = Contact()..objectId = 'contact2';
682+
683+
final order = Order();
684+
685+
// First, get the relation with typed generic (this creates _ParseRelation<Contact>)
686+
order.getRelation<Contact>('receivers');
687+
688+
// act & assert
689+
// This should NOT throw: _TypeError (type '_Set<ParseObject>' is not a subtype of type 'Set<Contact>' in type cast)
690+
expect(
691+
() => order.addRelation('receivers', [contact1, contact2]),
692+
returnsNormally,
693+
);
694+
},
695+
);
696+
697+
test(
698+
'calling addRelation() multiple times with custom subclasses should work',
699+
() {
700+
// arrange
701+
final contact1 = Contact()..objectId = 'contact1';
702+
final contact2 = Contact()..objectId = 'contact2';
703+
final contact3 = Contact()..objectId = 'contact3';
704+
705+
final order = Order();
706+
707+
// act & assert
708+
// First addRelation call
709+
expect(
710+
() => order.addRelation('receivers', [contact1]),
711+
returnsNormally,
712+
);
713+
714+
// Second addRelation call - this should also not throw
715+
expect(
716+
() => order.addRelation('receivers', [contact2, contact3]),
717+
returnsNormally,
718+
);
719+
},
720+
);
721+
722+
test('removeRelation() should work with custom ParseObject subclasses', () {
723+
// arrange
724+
final contact1 = Contact()..objectId = 'contact1';
725+
final contact2 = Contact()..objectId = 'contact2';
726+
727+
final order = Order();
728+
729+
// act & assert
730+
expect(
731+
() => order.removeRelation('receivers', [contact1, contact2]),
732+
returnsNormally,
733+
);
734+
});
627735
});
628736
}
737+
738+
/// Custom ParseObject subclass for testing (similar to the issue report)
739+
class Contact extends ParseObject implements ParseCloneable {
740+
Contact() : super(_keyTableName);
741+
Contact.clone() : this();
742+
743+
@override
744+
clone(Map<String, dynamic> map) =>
745+
Contact.clone()..fromJson(Map<String, dynamic>.from(map));
746+
747+
static const String _keyTableName = 'Contact';
748+
}
749+
750+
/// Custom ParseObject subclass for testing (similar to the issue report)
751+
class Order extends ParseObject implements ParseCloneable {
752+
Order() : super(_keyTableName);
753+
Order.clone() : this();
754+
755+
@override
756+
clone(Map<String, dynamic> map) =>
757+
Order.clone()..fromJson(Map<String, dynamic>.from(map));
758+
759+
static const String _keyTableName = 'Order';
760+
}

0 commit comments

Comments
 (0)