diff --git a/flow-client/src/main/resources/META-INF/resources/frontend/Connect.ts b/flow-client/src/main/resources/META-INF/resources/frontend/Connect.ts index b73d124a60f..6f47a026f4f 100644 --- a/flow-client/src/main/resources/META-INF/resources/frontend/Connect.ts +++ b/flow-client/src/main/resources/META-INF/resources/frontend/Connect.ts @@ -347,15 +347,14 @@ export class ConnectClient { // chain item for our convenience. Always having an ending of the chain // this way makes the folding down below more concise. const fetchNext: MiddlewareNext = async (context: MiddlewareContext): Promise => { - this.loadingStarted(); + $wnd.Vaadin.connectionState.loadingStarted(); return fetch(context.request) .then((response) => { - this.loadingFinished(); + $wnd.Vaadin.connectionState.loadingFinished(); return response; }) .catch((error) => { - this.loadingFinished(); - $wnd.Vaadin.connectionState.state = ConnectionState.CONNECTION_LOST; + $wnd.Vaadin.connectionState.loadingFailed(); return Promise.reject(error); }); }; @@ -389,24 +388,4 @@ export class ConnectClient { private isFlowLoaded(): boolean { return $wnd.Vaadin.Flow?.clients?.TypeScript !== undefined; } - - private loadingStarted() { - if (this.isFlowLoaded()) { - // call Flow.loadingStarted to pause TestBench tests while backend - // requests are ongoing - $wnd.Vaadin.Flow.clients?.TypeScript?.loadingStarted(); - } else { - $wnd.Vaadin.connectionState.loadingStarted(); - } - } - - private loadingFinished() { - if (this.isFlowLoaded()) { - // call Flow.loadingFinished to pause TestBench tests while backend - // requests are ongoing - $wnd.Vaadin.Flow.clients?.TypeScript?.loadingFinished(); - } else { - $wnd.Vaadin.connectionState.loadingFinished(); - } - } } diff --git a/flow-client/src/main/resources/META-INF/resources/frontend/ConnectionState.ts b/flow-client/src/main/resources/META-INF/resources/frontend/ConnectionState.ts index 66aa1cff3d5..e802096d3ff 100644 --- a/flow-client/src/main/resources/META-INF/resources/frontend/ConnectionState.ts +++ b/flow-client/src/main/resources/META-INF/resources/frontend/ConnectionState.ts @@ -70,10 +70,18 @@ export class ConnectionStateStore { } loadingFinished(): void { + this.decreaseLoadingCount(ConnectionState.CONNECTED); + } + + loadingFailed(): void { + this.decreaseLoadingCount(ConnectionState.CONNECTION_LOST); + } + + private decreaseLoadingCount(finalState: ConnectionState) { if (this.loadingCount > 0) { this.loadingCount -= 1; if (this.loadingCount === 0) { - this.state = ConnectionState.CONNECTED; + this.state = finalState; } } } diff --git a/flow-client/src/main/resources/META-INF/resources/frontend/Flow.ts b/flow-client/src/main/resources/META-INF/resources/frontend/Flow.ts index 4447d4bd9fb..d0118d7bb91 100644 --- a/flow-client/src/main/resources/META-INF/resources/frontend/Flow.ts +++ b/flow-client/src/main/resources/META-INF/resources/frontend/Flow.ts @@ -87,9 +87,7 @@ export class Flow { $wnd.Vaadin.Flow = $wnd.Vaadin.Flow || {}; $wnd.Vaadin.Flow.clients = { TypeScript: { - isActive: () => this.isActive, - loadingStarted: () => this.loadingStarted(), - loadingFinished: () => this.loadingFinished() + isActive: () => this.isActive } }; diff --git a/flow-client/src/test/frontend/ConnectTests.ts b/flow-client/src/test/frontend/ConnectTests.ts index 682c5035d97..8e9be38b62a 100644 --- a/flow-client/src/test/frontend/ConnectTests.ts +++ b/flow-client/src/test/frontend/ConnectTests.ts @@ -179,40 +179,19 @@ describe('ConnectClient', () => { expect(fetchMock.lastOptions()).to.include({method: 'POST'}); }); - it('should set CONNECTED followed by ConnectionState.loadingFinished when Flow is not loaded', async() => { + it('should set connection state to LOADING followed by CONNECTED on successful fetch', async() => { let $wnd = (window as any); - let states: Array = []; - $wnd.Vaadin.connectionState.addStateChangeListener( - (_, state) => states.push(state)); - await client.call('FooEndpoint', 'fooMethod'); - expect(states).to.deep.equal([ConnectionState.LOADING, ConnectionState.CONNECTED]); - }); + const stateChangeListener = sinon.fake(); + $wnd.Vaadin.connectionState.addStateChangeListener(stateChangeListener); - it('should call Flow.loadingStarted followed by Flow.loadingFinished when Flow is loaded', async() => { - let calls: Array = []; - (window as any).Vaadin.Flow = { - clients: { - TypeScript: { - loadingStarted: () => calls.push(true), - loadingFinished: () => calls.push(false) - } - } - }; await client.call('FooEndpoint', 'fooMethod'); - expect(calls).to.deep.equal([true, false]); + (expect(stateChangeListener).to.be as any).calledWithExactly(ConnectionState.LOADING, ConnectionState.CONNECTED); }); - it('should call Flow.loadingFinished and transition to CONNECTION_LOST upon network failure', async() => { - let calls: Array = []; - (window as any).Vaadin.Flow = { - clients: { - TypeScript: { - loadingStarted: () => calls.push(true), - loadingFinished: () => calls.push(false) - } - } - }; - + it('should set connection state to CONNECTION_LOST on network failure', async() => { + let $wnd = (window as any); + const stateChangeListener = sinon.fake(); + $wnd.Vaadin.connectionState.addStateChangeListener(stateChangeListener); fetchMock.post( base + '/connect/FooEndpoint/reject', Promise.reject(new TypeError('Network failure')) @@ -222,12 +201,12 @@ describe('ConnectClient', () => { } catch (error) { // expected } finally { - expect(calls).to.deep.equal([true, false]); - expect((window as any).Vaadin.connectionState.state).to.equal(ConnectionState.CONNECTION_LOST); + (expect(stateChangeListener).to.be as any).calledWithExactly(ConnectionState.LOADING, ConnectionState.CONNECTION_LOST); } }); - it('should call Flow.loadingFinished upon server error', async() => { + it('should set connection state to CONNECTED upon server error', async() => { + let $wnd = (window as any); const body = 'Unexpected error'; const errorResponse = new Response( body, @@ -238,21 +217,12 @@ describe('ConnectClient', () => { ); fetchMock.post(base + '/connect/FooEndpoint/vaadinConnectResponse', errorResponse); - let calls: Array = []; - (window as any).Vaadin.Flow = { - clients: { - TypeScript: { - loadingStarted: () => calls.push(true), - loadingFinished: () => calls.push(false) - } - } - }; try { await client.call('FooEndpoint', 'vaadinConnectResponse'); } catch (error) { // expected } finally { - expect(calls).to.deep.equal([true, false]); + expect($wnd.Vaadin.connectionState.state).to.equal(ConnectionState.CONNECTED); } }); diff --git a/flow-client/src/test/frontend/ConnectionStateTests.ts b/flow-client/src/test/frontend/ConnectionStateTests.ts index b05d3deb81e..2c218338aa8 100644 --- a/flow-client/src/test/frontend/ConnectionStateTests.ts +++ b/flow-client/src/test/frontend/ConnectionStateTests.ts @@ -12,27 +12,25 @@ describe('ConnectionStateStore', () => { it('should call state change listeners when transitioning between states', () => { const store = new ConnectionStateStore(ConnectionState.CONNECTED); - const stateChangeListener1 = sinon.spy((_: ConnectionState, __: ConnectionState) => { - }); - const stateChangeListener2 = sinon.spy((_: ConnectionState, __: ConnectionState) => { - }); - + const stateChangeListener1 = sinon.fake(); + const stateChangeListener2 = sinon.fake(); store.addStateChangeListener(stateChangeListener1); store.addStateChangeListener(stateChangeListener2); + (expect(stateChangeListener1).to.not.be as any).called; (expect(stateChangeListener2).to.not.be as any).called; store.state = ConnectionState.CONNECTION_LOST; + (expect(stateChangeListener1).to.be as any).calledOnce; (expect(stateChangeListener2).to.be as any).calledOnce; }); it('should have removable state change listeners', () => { const store = new ConnectionStateStore(ConnectionState.CONNECTED); - const stateChangeListener = sinon.spy((_: ConnectionState, __: ConnectionState) => { - }); - + const stateChangeListener = sinon.fake(); store.addStateChangeListener(stateChangeListener); + store.removeStateChangeListener(stateChangeListener); store.state = ConnectionState.CONNECTION_LOST; (expect(stateChangeListener).to.not.be as any).called; @@ -40,10 +38,9 @@ describe('ConnectionStateStore', () => { it('state change listeners should be idempotent with respect to state update', () => { const store = new ConnectionStateStore(ConnectionState.CONNECTED); - const stateChangeListener = sinon.spy((_: ConnectionState, __: ConnectionState) => { - }); - + const stateChangeListener = sinon.fake(); store.addStateChangeListener(stateChangeListener); + store.state = ConnectionState.CONNECTION_LOST; store.state = ConnectionState.CONNECTION_LOST; @@ -64,27 +61,25 @@ describe('ConnectionStateStore', () => { it('LOADING states are stacked', () => { const store = new ConnectionStateStore(ConnectionState.CONNECTED); - const states = Array<[ConnectionState,ConnectionState]>(); - const stateChangeListener = (prev: ConnectionState, next: ConnectionState) => { - states.push([prev, next]); - }; + const stateChangeListener = sinon.fake(); store.addStateChangeListener(stateChangeListener); store.loadingStarted(); store.loadingStarted(); store.loadingFinished(); store.loadingFinished(); - assert.deepEqual(states, - [[ConnectionState.CONNECTED, ConnectionState.LOADING], - [ConnectionState.LOADING, ConnectionState.CONNECTED]]); + + assert.equal(stateChangeListener.callCount, 2); + + (expect(stateChangeListener.getCall(0)).to.be as any).calledWithExactly( + ConnectionState.CONNECTED, ConnectionState.LOADING); + (expect(stateChangeListener.getCall(1)).to.be as any).calledWithExactly( + ConnectionState.LOADING, ConnectionState.CONNECTED); }); it('loading count should reset when state forced', () => { const store = new ConnectionStateStore(ConnectionState.CONNECTED); - const states = Array<[ConnectionState,ConnectionState]>(); - const stateChangeListener = (prev: ConnectionState, next: ConnectionState) => { - states.push([prev, next]); - }; + const stateChangeListener = sinon.fake(); store.addStateChangeListener(stateChangeListener); store.loadingStarted(); @@ -92,11 +87,31 @@ describe('ConnectionStateStore', () => { store.loadingStarted(); store.loadingFinished(); - assert.deepEqual(states, - [[ConnectionState.CONNECTED, ConnectionState.LOADING], - [ConnectionState.LOADING, ConnectionState.CONNECTION_LOST], - [ConnectionState.CONNECTION_LOST, ConnectionState.LOADING], - [ConnectionState.LOADING, ConnectionState.CONNECTED]]); + assert.equal(stateChangeListener.callCount, 4); + + (expect(stateChangeListener.getCall(0)).to.be as any).calledWithExactly( + ConnectionState.CONNECTED, ConnectionState.LOADING); + (expect(stateChangeListener.getCall(1)).to.be as any).calledWithExactly( + ConnectionState.LOADING, ConnectionState.CONNECTION_LOST); + (expect(stateChangeListener.getCall(2)).to.be as any).calledWithExactly( + ConnectionState.CONNECTION_LOST, ConnectionState.LOADING); + (expect(stateChangeListener.getCall(3)).to.be as any).calledWithExactly( + ConnectionState.LOADING, ConnectionState.CONNECTED); + }); + + it('loadingFailed should set state to CONNECTION_LOST', () => { + const store = new ConnectionStateStore(ConnectionState.CONNECTION_LOST); + const stateChangeListener = sinon.fake(); + store.addStateChangeListener(stateChangeListener); + + store.loadingStarted(); + store.loadingFailed(); + + assert.equal(stateChangeListener.callCount, 2); + (expect(stateChangeListener.getCall(0)).to.be as any).calledWithExactly( + ConnectionState.CONNECTION_LOST, ConnectionState.LOADING); + (expect(stateChangeListener.getCall(1)).to.be as any).calledWithExactly( + ConnectionState.LOADING, ConnectionState.CONNECTION_LOST); }); it('should request offline information from from service worker', async() => { diff --git a/flow-client/src/test/frontend/FlowTests.ts b/flow-client/src/test/frontend/FlowTests.ts index a135b584172..05470f54bf0 100644 --- a/flow-client/src/test/frontend/FlowTests.ts +++ b/flow-client/src/test/frontend/FlowTests.ts @@ -323,10 +323,6 @@ suite("Flow", () => { assert.isDefined($wnd.Vaadin.Flow.clients.TypeScript.isActive); assert.isFalse($wnd.Vaadin.Flow.clients.TypeScript.isActive()); - // Check that loadingStarted and loadingFinished are exposed - assert.isDefined($wnd.Vaadin.Flow.clients.TypeScript.loadingStarted); - assert.isDefined($wnd.Vaadin.Flow.clients.TypeScript.loadingFinished); - const route = flow.serverSideRoutes[0]; sinon.spy(flow, 'loadingStarted');