@@ -744,7 +744,7 @@ void main() {
744744 expect ((trent.state as A ).value, 111 ); // updated from 110 to 111
745745 await Future .delayed (const Duration (milliseconds: 5 ));
746746 attempt3.reject ();
747- expect ((trent.state as A ).value, 11 ); // was 10, should be 11
747+ expect ((trent.state as A ).value, 11 ); // should be 11
748748
749749 // Try two different tags (should not collide)
750750 final a = trent.optimisticUpdate <int >(
@@ -759,13 +759,114 @@ void main() {
759759 reverse: (state, value) => (state as A ).copyWith (value: state.value - value),
760760 );
761761 b.execute (2 );
762- expect ((trent.state as A ).value, 15 ); // updated from 14 to 15
762+ expect ((trent.state as A ).value, 14 );
763763 await Future .delayed (const Duration (milliseconds: 5 ));
764764 a.acceptAs (1 ); // AcceptAs with same value (no change)
765- expect ((trent.state as A ).value, 15 );
765+ expect ((trent.state as A ).value, 14 );
766766 b.reject ();
767- expect ((trent.state as A ).value, 13 );
767+ expect ((trent.state as A ).value, 12 );
768768 });
769769
770- // todo later (needed?): all trent optimistic updates should be wrapped in cancelableAsyncOp
770+ test ('OptimisticAttempt accept, reject, acceptAs with same and different tags' , () async {
771+ final trent = SimpleTrent ();
772+ expect ((trent.state as A ).value, 0 );
773+
774+ // Same tag: accept, reject, acceptAs
775+ final att1 = trent.optimisticUpdate <int >(
776+ tag: 'foo' ,
777+ forward: (state, value) => (state as A ).copyWith (value: state.value + value),
778+ reverse: (state, value) => (state as A ).copyWith (value: state.value - value),
779+ );
780+ att1.execute (5 );
781+ expect ((trent.state as A ).value, 5 );
782+ att1.accept (); // lock in
783+ // New attempt with same tag should NOT revert previous
784+ final att2 = trent.optimisticUpdate <int >(
785+ tag: 'foo' ,
786+ forward: (state, value) => (state as A ).copyWith (value: state.value + value),
787+ reverse: (state, value) => (state as A ).copyWith (value: state.value - value),
788+ );
789+ att2.execute (10 );
790+ expect ((trent.state as A ).value, 15 ); // 5 (locked) + 10
791+ att2.reject ();
792+ expect ((trent.state as A ).value, 5 ); // back to locked in value
793+
794+ // Now test acceptAs
795+ final att3 = trent.optimisticUpdate <int >(
796+ tag: 'foo' ,
797+ forward: (state, value) => (state as A ).copyWith (value: state.value + value),
798+ reverse: (state, value) => (state as A ).copyWith (value: state.value - value),
799+ );
800+ att3.execute (2 );
801+ expect ((trent.state as A ).value, 7 );
802+ att3.acceptAs (20 ); // should revert 2, then apply 20
803+ expect ((trent.state as A ).value,
804+ 25 ); // 5 (locked) - 2 + 20 = 23, but since att3 was on top of 5, 5+2=7, revert 2 (7-2=5), then +20=25
805+
806+ // Now test with different tags (should stack)
807+ final attA = trent.optimisticUpdate <int >(
808+ tag: 'A' ,
809+ forward: (state, value) => (state as A ).copyWith (value: state.value + value),
810+ reverse: (state, value) => (state as A ).copyWith (value: state.value - value),
811+ );
812+ attA.execute (1 );
813+ expect ((trent.state as A ).value, 26 );
814+ final attB = trent.optimisticUpdate <int >(
815+ tag: 'B' ,
816+ forward: (state, value) => (state as A ).copyWith (value: state.value + value),
817+ reverse: (state, value) => (state as A ).copyWith (value: state.value - value),
818+ );
819+ attB.execute (2 );
820+ expect ((trent.state as A ).value, 28 );
821+ attA.reject ();
822+ expect ((trent.state as A ).value, 27 );
823+ attB.accept ();
824+ // Now new B should not revert previous
825+ final attB2 = trent.optimisticUpdate <int >(
826+ tag: 'B' ,
827+ forward: (state, value) => (state as A ).copyWith (value: state.value + value),
828+ reverse: (state, value) => (state as A ).copyWith (value: state.value - value),
829+ );
830+ attB2.execute (3 );
831+ expect ((trent.state as A ).value, 30 );
832+ attB2.reject ();
833+ expect ((trent.state as A ).value, 27 );
834+ });
835+
836+ test ('OptimisticAttempt: multiple pending, flood, and fallback to previous' , () async {
837+ final trent = SimpleTrent ();
838+ expect ((trent.state as A ).value, 0 );
839+
840+ // Start first optimistic attempt (pending)
841+ final att1 = trent.optimisticUpdate <int >(
842+ tag: 'flood' ,
843+ forward: (state, value) => (state as A ).copyWith (value: state.value + value),
844+ reverse: (state, value) => (state as A ).copyWith (value: state.value - value),
845+ );
846+ att1.execute (1 );
847+ expect ((trent.state as A ).value, 1 );
848+
849+ // Start a second attempt before the first resolves (flood/collision)
850+ final att2 = trent.optimisticUpdate <int >(
851+ tag: 'flood' , // same tag, so it should replace attempt1
852+ forward: (state, value) => (state as A ).copyWith (value: state.value + value),
853+ reverse: (state, value) => (state as A ).copyWith (value: state.value - value),
854+ );
855+ att2.execute (10 );
856+ expect ((trent.state as A ).value, 10 ); // corrected from 11 to 10
857+
858+ // Start a third optimistic attempt (floods, replaces att2)
859+ final att3 = trent.optimisticUpdate <int >(
860+ tag: 'flood' ,
861+ forward: (state, value) => (state as A ).copyWith (value: state.value + value),
862+ reverse: (state, value) => (state as A ).copyWith (value: state.value - value),
863+ );
864+ att3.execute (100 );
865+ expect ((trent.state as A ).value, 100 );
866+
867+ // att3 fails (reject)
868+ att3.reject ();
869+ // Should revert att3's effect, fallback to base state (not att2's effect)
870+ expect ((trent.state as A ).value, 0 );
871+ });
771872}
0 commit comments