diff --git a/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/ValueSignal.java b/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/ValueSignal.java index 6a006e4cd6..fd2b041305 100644 --- a/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/ValueSignal.java +++ b/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/ValueSignal.java @@ -74,9 +74,8 @@ public Flux subscribe() { LOGGER.debug("New Flux subscription..."); lock.lock(); try { - var currentValue = createStatusUpdateEvent(this.id.toString(), - StateEvent.EventType.SNAPSHOT); - sink.tryEmitNext(currentValue); + var snapshot = createSnapshotEvent(); + sink.tryEmitNext(snapshot); subscribers.add(sink); } finally { lock.unlock(); @@ -102,14 +101,11 @@ public Flux subscribe() { public void submit(ObjectNode event) { lock.lock(); try { - boolean success = processEvent(event); + boolean accepted = processEvent(event); // Notify subscribers subscribers.removeIf(sink -> { - var updatedValue = createStatusUpdateEvent( - event.get("id").asText(), - success ? StateEvent.EventType.SNAPSHOT - : StateEvent.EventType.REJECT); - boolean failure = sink.tryEmitNext(updatedValue).isFailure(); + var eventWithStatus = StateEvent.setAccepted(event, accepted); + boolean failure = sink.tryEmitNext(eventWithStatus).isFailure(); if (failure) { LOGGER.debug("Failed push"); } @@ -139,10 +135,10 @@ public T getValue() { return this.value; } - private ObjectNode createStatusUpdateEvent(String eventId, - StateEvent.EventType eventType) { - var snapshot = new StateEvent<>(eventId, eventType, this.value); - return snapshot.toJson(); + private ObjectNode createSnapshotEvent() { + var snapshot = new StateEvent<>(getId().toString(), + StateEvent.EventType.SNAPSHOT, this.value).toJson(); + return StateEvent.setAccepted(snapshot, true); } /** diff --git a/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/core/event/StateEvent.java b/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/core/event/StateEvent.java index bbb0992726..acfefc5327 100644 --- a/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/core/event/StateEvent.java +++ b/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/core/event/StateEvent.java @@ -25,13 +25,14 @@ public static final class Field { public static final String TYPE = "type"; public static final String VALUE = "value"; public static final String EXPECTED = "expected"; + public static final String ACCEPTED = "accepted"; } /** * Possible types of state events. */ public enum EventType { - SNAPSHOT, SET, REPLACE, REJECT, INCREMENT + SNAPSHOT, SET, REPLACE, INCREMENT } /** @@ -165,6 +166,15 @@ public ObjectNode toJson() { return json; } + public static ObjectNode setAccepted(ObjectNode event, boolean accepted) { + return event.put(Field.ACCEPTED, accepted); + } + + public static boolean isAccepted(ObjectNode event) { + return event.has(Field.ACCEPTED) + && event.get(Field.ACCEPTED).asBoolean(); + } + private JsonNode valueAsJsonNode(T value) { return MAPPER.valueToTree(value); } diff --git a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/ValueSignalTest.java b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/ValueSignalTest.java index 2bfde9dade..1ceed46735 100644 --- a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/ValueSignalTest.java +++ b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/ValueSignalTest.java @@ -12,9 +12,11 @@ import org.junit.Test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; public class ValueSignalTest { @@ -96,9 +98,13 @@ public void submit_notifies_subscribers() { // notification for the initial value assertNull(stateEvent.getValue()); } else if (counter.get() == 1) { + assertTrue(StateEvent.isAccepted(eventJson)); assertEquals(name, stateEvent.getValue().getName()); + assertEquals(name, signal.getValue().getName()); assertEquals(age, stateEvent.getValue().getAge()); + assertEquals(age, signal.getValue().getAge()); assertEquals(adult, stateEvent.getValue().isAdult()); + assertEquals(adult, signal.getValue().isAdult()); } counter.incrementAndGet(); }); @@ -123,12 +129,14 @@ public void submit_conditionIsMet_notifies_subscribers_with_snapshot_event() { if (counter.get() == 0) { // notification for the initial value assertEquals(2.0, stateEvent.getValue(), 0.0); + assertTrue(StateEvent.isAccepted(eventJson)); } else if (counter.get() == 1) { assertEquals(conditionalReplaceEvent.get(StateEvent.Field.ID) .asText(), stateEvent.getId()); - assertEquals(StateEvent.EventType.SNAPSHOT, + assertEquals(StateEvent.EventType.REPLACE, stateEvent.getEventType()); - assertEquals(3.0, stateEvent.getValue(), 0.0); + assertTrue(StateEvent.isAccepted(eventJson)); + assertEquals(3.0, signal.getValue(), 0.0); } counter.incrementAndGet(); }); @@ -151,13 +159,14 @@ public void submit_conditionIsNotMet_notifies_subscribers_with_reject_event() { var stateEvent = new StateEvent<>(eventJson, Double.class); if (counter.get() == 0) { // notification for the initial value - assertEquals(1.0, stateEvent.getValue(), 0.0); + assertTrue(StateEvent.isAccepted(eventJson)); } else if (counter.get() == 1) { assertEquals(conditionalReplaceEvent.get(StateEvent.Field.ID) .asText(), stateEvent.getId()); - assertEquals(StateEvent.EventType.REJECT, + assertEquals(StateEvent.EventType.REPLACE, stateEvent.getEventType()); - assertEquals(1.0, stateEvent.getValue(), 0.0); + assertFalse(StateEvent.isAccepted(eventJson)); + assertEquals(1.0, signal.getValue(), 0.0); } counter.incrementAndGet(); }); diff --git a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/core/event/StateEventTest.java b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/core/event/StateEventTest.java index c58a10f198..36e6ae9885 100644 --- a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/core/event/StateEventTest.java +++ b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/core/event/StateEventTest.java @@ -133,7 +133,7 @@ public void constructor_withJsonInvalidEventType_shouldThrowInvalidEventTypeExce StateEvent.InvalidEventTypeException.class, () -> new StateEvent<>(json, String.class)); - String expectedMessage = "Invalid event type invalidType. Type should be one of: [SNAPSHOT, SET, REPLACE, REJECT, INCREMENT]"; + String expectedMessage = "Invalid event type invalidType. Type should be one of: [SNAPSHOT, SET, REPLACE, INCREMENT]"; String actualMessage = exception.getMessage(); assertTrue(actualMessage.contains(expectedMessage)); @@ -152,7 +152,7 @@ public void constructor_withJsonMissingEventType_shouldThrowInvalidEventTypeExce StateEvent.InvalidEventTypeException.class, () -> new StateEvent<>(json, String.class)); - String expectedMessage = "Missing event type. Type is required, and should be one of: [SNAPSHOT, SET, REPLACE, REJECT, INCREMENT]"; + String expectedMessage = "Missing event type. Type is required, and should be one of: [SNAPSHOT, SET, REPLACE, INCREMENT]"; String actualMessage = exception.getMessage(); assertTrue(actualMessage.contains(expectedMessage)); diff --git a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/handler/SignalsHandlerTest.java b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/handler/SignalsHandlerTest.java index dd813e4743..c318e831a2 100644 --- a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/handler/SignalsHandlerTest.java +++ b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/handler/SignalsHandlerTest.java @@ -96,7 +96,8 @@ public void when_signalIsRegistered_update_notifiesTheSubscribers() var expectedUpdatedSignalEventJson = new ObjectNode( mapper.getNodeFactory()).put("value", 42.0) - .put("id", signalId.toString()).put("type", "snapshot"); + .put("id", signalId.toString()).put("type", "snapshot") + .put("accepted", true); StepVerifier.create(firstFlux) .expectNext(expectedUpdatedSignalEventJson).thenCancel() .verify(); diff --git a/packages/ts/react-signals/src/NumberSignal.ts b/packages/ts/react-signals/src/NumberSignal.ts index ccf93cd2a9..1284d7ad1c 100644 --- a/packages/ts/react-signals/src/NumberSignal.ts +++ b/packages/ts/react-signals/src/NumberSignal.ts @@ -1,5 +1,5 @@ -import { createIncrementStateEvent } from './events.js'; -import { $update } from './FullStackSignal.js'; +import { createIncrementStateEvent, type StateEvent } from './events.js'; +import { $processServerResponse, $update } from './FullStackSignal.js'; import { ValueSignal } from './ValueSignal.js'; /** @@ -26,6 +26,7 @@ import { ValueSignal } from './ValueSignal.js'; * ``` */ export class NumberSignal extends ValueSignal { + readonly #sentIncrementEvents = new Map>(); /** * Increments the value by the specified delta. The delta can be negative to * decrease the value. @@ -44,6 +45,19 @@ export class NumberSignal extends ValueSignal { } this.setValueLocal(this.value + delta); const event = createIncrementStateEvent(delta); + this.#sentIncrementEvents.set(event.id, event); this[$update](event); } + + protected override [$processServerResponse](event: StateEvent): void { + if (event.accepted && event.type === 'increment') { + if (this.#sentIncrementEvents.has(event.id)) { + this.#sentIncrementEvents.delete(event.id); + return; + } + this.setValueLocal(this.value + event.value); + } else { + super[$processServerResponse](event); + } + } } diff --git a/packages/ts/react-signals/src/ValueSignal.ts b/packages/ts/react-signals/src/ValueSignal.ts index 0760d8a0da..d1eb811bc5 100644 --- a/packages/ts/react-signals/src/ValueSignal.ts +++ b/packages/ts/react-signals/src/ValueSignal.ts @@ -80,17 +80,27 @@ export class ValueSignal extends FullStackSignal { this.#pendingRequests.delete(event.id); } - if (event.type === 'snapshot') { + if (!event.accepted && record) { + if (!record.canceled) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.update(record.callback); + } + } + + if (event.accepted || event.type === 'snapshot') { if (record) { record.waiter.resolve(); } - this.value = event.value; + this.#applyAcceptedEvent(event); } + } - if (event.type === 'reject' && record) { - if (!record.canceled) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.update(record.callback); + #applyAcceptedEvent(event: StateEvent): void { + if (event.type === 'set' || event.type === 'snapshot') { + this.value = event.value; + } else if (event.type === 'replace') { + if (JSON.stringify(this.value) === JSON.stringify(event.expected)) { + this.value = event.value; } } } diff --git a/packages/ts/react-signals/src/events.ts b/packages/ts/react-signals/src/events.ts index e47c0901f6..b29da7e35e 100644 --- a/packages/ts/react-signals/src/events.ts +++ b/packages/ts/react-signals/src/events.ts @@ -9,6 +9,7 @@ type CreateStateEventType id: string; type: T; value: V; + accepted: boolean; }> & Readonly >; @@ -19,11 +20,8 @@ type CreateStateEventType */ export type SnapshotStateEvent = CreateStateEventType; -export type RejectStateEvent = CreateStateEventType; - /** * A state event defines a new value of the signal shared with the server. The - * */ export type SetStateEvent = CreateStateEventType; @@ -32,6 +30,7 @@ export function createSetStateEvent(value: T): SetStateEvent { id: nanoid(), type: 'set', value, + accepted: false, }; } @@ -43,6 +42,7 @@ export function createReplaceStateEvent(expected: T, value: T): ReplaceStateE type: 'replace', value, expected, + accepted: false, }; } @@ -53,15 +53,11 @@ export function createIncrementStateEvent(delta: number): IncrementStateEvent { id: nanoid(), type: 'increment', value: delta, + accepted: false, }; } /** * An object that describes the change of the signal state. */ -export type StateEvent = - | IncrementStateEvent - | RejectStateEvent - | ReplaceStateEvent - | SetStateEvent - | SnapshotStateEvent; +export type StateEvent = IncrementStateEvent | ReplaceStateEvent | SetStateEvent | SnapshotStateEvent; diff --git a/packages/ts/react-signals/test/FullStackSignal.spec.tsx b/packages/ts/react-signals/test/FullStackSignal.spec.tsx index 399671dfaa..2369d2c9ca 100644 --- a/packages/ts/react-signals/test/FullStackSignal.spec.tsx +++ b/packages/ts/react-signals/test/FullStackSignal.spec.tsx @@ -5,7 +5,7 @@ import { ActionOnLostSubscription, ConnectClient, type Subscription } from '@vaa import { nanoid } from 'nanoid'; import sinon from 'sinon'; import sinonChai from 'sinon-chai'; -import type { SnapshotStateEvent, StateEvent } from '../src/events.js'; +import type { StateEvent } from '../src/events.js'; import { DependencyTrackingSignal } from '../src/FullStackSignal.js'; import { computed, NumberSignal } from '../src/index.js'; import { nextFrame } from './utils.js'; @@ -51,8 +51,11 @@ describe('@vaadin/hilla-react-signals', () => { }); describe('FullStackSignal', () => { - function createSnapshotEvent(value: number): SnapshotStateEvent { - return { id: nanoid(), type: 'snapshot', value }; + function createAcceptedEvent( + value: number, + type: 'increment' | 'replace' | 'set' | 'snapshot', + ): StateEvent { + return { id: nanoid(), type, value, expected: 0, accepted: true }; } function simulateReceivedChange( @@ -195,7 +198,7 @@ describe('@vaadin/hilla-react-signals', () => { await nextFrame(); // Simulate the event received from the server: - const snapshotEvent = createSnapshotEvent(42); + const snapshotEvent = createAcceptedEvent(42, 'snapshot'); simulateReceivedChange(subscription, snapshotEvent); // Check if the signal value is updated: @@ -207,13 +210,13 @@ describe('@vaadin/hilla-react-signals', () => { let result = render(Value is {numberSignal}); await nextFrame(); - simulateReceivedChange(subscription, createSnapshotEvent(42)); + simulateReceivedChange(subscription, createAcceptedEvent(42, 'snapshot')); result = render(Value is {numberSignal}); await nextFrame(); expect(result.container.textContent).to.equal('Value is 42'); - simulateReceivedChange(subscription, createSnapshotEvent(99)); + simulateReceivedChange(subscription, createAcceptedEvent(99, 'set')); await nextFrame(); expect(result.container.textContent).to.equal('Value is 99'); }); diff --git a/packages/ts/react-signals/test/NumberSignal.spec.tsx b/packages/ts/react-signals/test/NumberSignal.spec.tsx index 1a79837858..012d12b779 100644 --- a/packages/ts/react-signals/test/NumberSignal.spec.tsx +++ b/packages/ts/react-signals/test/NumberSignal.spec.tsx @@ -5,6 +5,7 @@ import { ConnectClient, type Subscription } from '@vaadin/hilla-frontend'; import chaiLike from 'chai-like'; import sinon from 'sinon'; import sinonChai from 'sinon-chai'; +import type { IncrementStateEvent } from '../events.js'; import type { StateEvent } from '../src/events.js'; import type { ServerConnectionConfig } from '../src/FullStackSignal.js'; import { effect, NumberSignal } from '../src/index.js'; @@ -18,9 +19,9 @@ describe('@vaadin/hilla-react-signals', () => { let subscription: sinon.SinonSpiedInstance>>; let client: sinon.SinonStubbedInstance; - function simulateReceivingSnapshot(eventId: string, value: number): void { + function simulateReceivingAcceptedEvent(event: StateEvent): void { const [onNextCallback] = subscription.onNext.firstCall.args; - onNextCallback({ id: eventId, type: 'snapshot', value }); + onNextCallback({ ...event, accepted: true }); } beforeEach(() => { @@ -78,35 +79,45 @@ describe('@vaadin/hilla-react-signals', () => { numberSignal.incrementBy(1); const [, , params1] = client.call.firstCall.args; + const expectedEvent1: IncrementStateEvent = { + // @ts-expect-error params.event type has id property + id: params1?.event.id, + type: 'increment', + value: 1, + accepted: false, + }; expect(client.call).to.have.been.calledWithMatch('SignalsHandler', 'update', { clientSignalId: numberSignal.id, - // @ts-expect-error params.event type has id property - event: { id: params1?.event.id, type: 'increment', value: 1 }, + event: expectedEvent1, }); - // @ts-expect-error params.event type has id property - simulateReceivingSnapshot(params1?.event.id, 43); + simulateReceivingAcceptedEvent(expectedEvent1); expect(numberSignal.value).to.equal(43); numberSignal.incrementBy(2); const [, , params2] = client.call.secondCall.args; + // @ts-expect-error params.event type has id property + const expectedEvent2: IncrementStateEvent = { id: params2?.event.id, type: 'increment', value: 2 }; expect(client.call).to.have.been.calledWithMatch('SignalsHandler', 'update', { clientSignalId: numberSignal.id, - // @ts-expect-error params.event type has id property - event: { id: params2?.event.id, type: 'increment', value: 2 }, + event: expectedEvent2, }); - // @ts-expect-error params.event type has id property - simulateReceivingSnapshot(params2?.event.id, 45); + simulateReceivingAcceptedEvent(expectedEvent2); expect(numberSignal.value).to.equal(45); numberSignal.incrementBy(-5); const [, , params3] = client.call.thirdCall.args; + const expectedEvent3: IncrementStateEvent = { + // @ts-expect-error params.event type has id property + id: params3?.event.id, + type: 'increment', + value: -5, + accepted: false, + }; expect(client.call).to.have.been.calledWithMatch('SignalsHandler', 'update', { clientSignalId: numberSignal.id, - // @ts-expect-error params.event type has id property - event: { id: params3?.event.id, type: 'increment', value: -5 }, + event: expectedEvent3, }); - // @ts-expect-error params.event type has id property - simulateReceivingSnapshot(params3?.event.id, 40); + simulateReceivingAcceptedEvent(expectedEvent3); expect(numberSignal.value).to.equal(40); }); @@ -131,5 +142,15 @@ describe('@vaadin/hilla-react-signals', () => { numberSignal.incrementBy(0); expect(client.call).not.to.have.been.called; }); + + it('should only apply the change to the value upon receiving accepted event that is not initiated by the NumberSignal itself', () => { + const numberSignal = new NumberSignal(42, config); + subscribeToSignalViaEffect(numberSignal); + + const expectedEvent: IncrementStateEvent = { id: 'testId', type: 'increment', value: 1, accepted: true }; + simulateReceivingAcceptedEvent(expectedEvent); + + expect(numberSignal.value).to.equal(43); + }); }); }); diff --git a/packages/ts/react-signals/test/ValueSignal.spec.tsx b/packages/ts/react-signals/test/ValueSignal.spec.tsx index 9367ca4e79..16ce260f3a 100644 --- a/packages/ts/react-signals/test/ValueSignal.spec.tsx +++ b/packages/ts/react-signals/test/ValueSignal.spec.tsx @@ -133,7 +133,7 @@ describe('@vaadin/hilla-react-signals', () => { expect(valueSignal.value).to.equal('baz'); }); - it('should send the correct event and update the value when receiving snapshot event after calling update', async () => { + it('should send the correct event and update the value when receiving accepted event after calling update', async () => { const valueSignal = new ValueSignal('ba', config); render(
{valueSignal}
); await nextFrame(); @@ -142,15 +142,16 @@ describe('@vaadin/hilla-react-signals', () => { const [, , params] = client.call.firstCall.args; expect(client.call).to.have.been.calledOnce; + // @ts-expect-error params.event type has id property + const expectedEvent = { id: params?.event.id, type: 'replace', value: 'bar', expected: 'ba' }; expect(client.call).to.have.been.calledWithMatch('SignalsHandler', 'update', { clientSignalId: valueSignal.id, - // @ts-expect-error params.event type has id property - event: { id: params?.event.id, type: 'replace', value: 'bar', expected: 'ba' }, + event: expectedEvent, }); const [onNextCallback] = subscription.onNext.firstCall.args; // @ts-expect-error params.event type has id property - onNextCallback({ id: params?.event.id, type: 'snapshot', value: 'bar' }); + onNextCallback({ ...expectedEvent, accepted: true }); expect(valueSignal.value).to.equal('bar'); }); @@ -171,12 +172,12 @@ describe('@vaadin/hilla-react-signals', () => { const [onNextCallback] = subscription.onNext.firstCall.args; - // Simulate a snapshot event representing a concurrent value change before the reject is received: - onNextCallback({ id: 'another-event-id', type: 'snapshot', value: 'b' }); + // Simulate an accepted event representing a concurrent value change before the reject is received: + onNextCallback({ id: 'another-event-id', type: 'replace', value: 'b', expected: 'a', accepted: true }); expect(valueSignal.value).to.equal('b'); // @ts-expect-error params.event type has id property - onNextCallback({ id: params1?.event.id, type: 'reject', value: 'dont care' }); + onNextCallback({ id: params1?.event.id, type: 'replace', value: 'aa', expected: 'a', accepted: false }); // verify that the value is not updated after receiving a reject event: expect(valueSignal.value).to.equal('b'); // verify that receiving reject event triggers another update call: @@ -185,54 +186,52 @@ describe('@vaadin/hilla-react-signals', () => { expect(client.call).to.have.been.calledWithMatch('SignalsHandler', 'update', { clientSignalId: valueSignal.id, // @ts-expect-error params.event type has id property - event: { id: params2?.event.id, type: 'replace', value: 'ba', expected: 'b' }, + event: { id: params2?.event.id, type: 'replace', value: 'ba', expected: 'b', accepted: false }, }); // Simulate another concurrent value change before the reject is received: - onNextCallback({ id: 'another-event-id', type: 'snapshot', value: 'c' }); + onNextCallback({ id: 'another-event-id', type: 'replace', value: 'c', expected: 'b', accepted: true }); expect(valueSignal.value).to.equal('c'); // @ts-expect-error params.event type has id property - onNextCallback({ id: params2?.event.id, type: 'reject' }); + onNextCallback({ id: params2?.event.id, type: 'replace', value: 'ba', expected: 'b', accepted: false }); expect(client.call).to.have.been.calledThrice; const [, , params3] = client.call.thirdCall.args; expect(client.call).to.have.been.calledWithMatch('SignalsHandler', 'update', { clientSignalId: valueSignal.id, // @ts-expect-error params.event type has id property - event: { id: params3?.event.id, type: 'replace', value: 'ca', expected: 'c' }, + event: { id: params3?.event.id, type: 'replace', value: 'ca', expected: 'c', accepted: false }, }); // @ts-expect-error params.event type has id property - onNextCallback({ id: params3?.event.id, type: 'reject', value: 'dont care' }); + onNextCallback({ id: params3?.event.id, type: 'replace', value: 'ca', expected: 'c', accepted: false }); expect(client.call).to.have.been.callCount(4); setTimeout(() => updateOperation.cancel(), 500); expect(valueSignal.value).to.equal('c'); }); - it('should update the value when receive snapshot event following reject events after calling update', async () => { + it('should update the value when receive accepted event following rejected events after calling update', async () => { const valueSignal = new ValueSignal('foo', config); expect(valueSignal.value).to.equal('foo'); render(
{valueSignal}
); await nextFrame(); - const [onNextCallback] = subscription.onNext.firstCall.args; - valueSignal.update((currValue) => `${currValue}bar`); const [, , params1] = client.call.firstCall.args; // @ts-expect-error params.event type has id property - onNextCallback({ id: params1?.event.id, type: 'reject', value: 'dont care' }); + onNextCallback({ id: params1?.event.id, type: 'replace', value: 'dont care', accepted: false }); expect(valueSignal.value).to.equal('foo'); const [, , params2] = client.call.secondCall.args; // @ts-expect-error params.event type has id property - onNextCallback({ id: params2?.event.id, type: 'reject', value: 'dont care' }); + onNextCallback({ id: params2?.event.id, type: 'replace', value: 'dont care', accepted: false }); expect(valueSignal.value).to.equal('foo'); const [, , params3] = client.call.thirdCall.args; // @ts-expect-error params.event type has id property - onNextCallback({ id: params3?.event.id, type: 'snapshot', value: 'foobar' }); + onNextCallback({ id: params3?.event.id, type: 'replace', value: 'foobar', expected: 'foo', accepted: true }); expect(valueSignal.value).to.equal('foobar'); }); });