From f55402b3356f502c5fa66e46033f179ef290a44b Mon Sep 17 00:00:00 2001 From: unadlib Date: Mon, 3 Oct 2022 22:52:09 +0800 Subject: [PATCH] fix(reactant-share): fix reactant-share router issue about back-forward routing --- .../reactant-module/src/core/createState.ts | 2 +- packages/reactant-router/src/router.tsx | 78 ++-- packages/reactant-share/src/constants.ts | 3 +- packages/reactant-share/src/interfaces.ts | 23 +- packages/reactant-share/src/portDetector.ts | 4 + packages/reactant-share/src/router.ts | 418 +++++++++--------- packages/reactant-share/test/router.test.tsx | 117 ++++- 7 files changed, 367 insertions(+), 278 deletions(-) diff --git a/packages/reactant-module/src/core/createState.ts b/packages/reactant-module/src/core/createState.ts index 9bca9535f..2bc41bb0f 100644 --- a/packages/reactant-module/src/core/createState.ts +++ b/packages/reactant-module/src/core/createState.ts @@ -1,4 +1,4 @@ -import { Reducer, Action, AnyAction } from 'redux'; +import type { Reducer, Action, AnyAction } from 'redux'; /** * ## Description diff --git a/packages/reactant-router/src/router.tsx b/packages/reactant-router/src/router.tsx index 44391abb6..c07591425 100644 --- a/packages/reactant-router/src/router.tsx +++ b/packages/reactant-router/src/router.tsx @@ -6,9 +6,9 @@ import { connectRouter, ConnectedRouter, CALL_HISTORY_METHOD, - LOCATION_CHANGE, RouterAction, onLocationChanged, + routerActions, } from 'connected-react-router'; import type { Location, LocationState, Action, History } from 'history'; @@ -46,30 +46,17 @@ export interface IRouterOptions { @injectable({ name: 'Router', }) -abstract class BaseReactantRouter extends PluginModule { - readonly [storeKey]?: Store; - +abstract class ReactantRouter extends PluginModule { autoProvide: boolean; protected history!: History; - abstract push(path: string, state?: Record): void; - - abstract replace( - path: string, - state?: Record - ): Promise | void; - - abstract go(n: number): Promise | void; - - abstract goBack(): Promise | void; - - abstract goForward(): Promise | void; - autoCreateHistory: boolean; onLocationChanged = onLocationChanged; + routerActions = routerActions; + protected readonly stateKey = 'router'; constructor(@inject(RouterOptions) protected options: IRouterOptions) { @@ -81,15 +68,9 @@ abstract class BaseReactantRouter extends PluginModule { this.history = this.options.createHistory(); this.middleware = (store) => (next) => (action: RouterAction) => { if (action.type !== CALL_HISTORY_METHOD) { - if ( - action.type === LOCATION_CHANGE && - action?.payload?.isFirstRendering === false && - this.history.location !== action.payload.location - ) { - this.history.replace(action.payload.location); - } return next(action); } + // Just call `history` method and `history` will change routing, then trigger `history.listen`, and dispatch LOCATION_CHANGE action const { payload: { method, args = [] }, } = action; @@ -99,61 +80,62 @@ abstract class BaseReactantRouter extends PluginModule { } } + get store() { + return this[storeKey]; + } + beforeCombineRootReducers(reducers: ReducersMapObject): ReducersMapObject { - if (!this.autoCreateHistory) return reducers; if (Object.prototype.hasOwnProperty.call(reducers, this.stateKey)) { throw new Error( `The identifier '${this.stateKey}' has a duplicate name, please reset the option 'stateKey' of 'ReactantRouter' module.` ); } return Object.assign(reducers, { - [this.stateKey]: connectRouter(this.history), + [this.stateKey]: connectRouter(this.history ?? this.defaultHistory), }); } + protected defaultHistory?: { + location: History['location']; + action: string; + }; + ConnectedRouter: FunctionComponent = (props) => ( {props.children} ); - get router(): RouterState { - return this[storeKey]?.getState()[this.stateKey]; + get router(): RouterState | undefined { + return this.store?.getState()[this.stateKey]; } get currentPath() { return this.router?.location.pathname; } - provider = (props: PropsWithChildren) => { - if (!this.autoProvide) return props.children; - return {props.children}; - }; -} - -@injectable() -class ReactantRouter extends BaseReactantRouter { - constructor(@inject(RouterOptions) public options: IRouterOptions) { - super(options); - } - - push(path: string, state?: Record) { - this.history.push(path, state); + push(path: string, state?: LocationState) { + this.store?.dispatch(this.routerActions.push(path, state)); } - replace(path: string, state?: Record) { - this.history.replace(path, state); + replace(path: string, state?: LocationState) { + this.store?.dispatch(this.routerActions.replace(path, state)); } go(n: number) { - this.history.go(n); + this.store?.dispatch(this.routerActions.go(n)); } goBack() { - this.history.goBack(); + this.store?.dispatch(this.routerActions.goBack()); } goForward() { - this.history.goForward(); + this.store?.dispatch(this.routerActions.goForward()); } + + provider = (props: PropsWithChildren) => { + if (!this.autoProvide) return props.children; + return {props.children}; + }; } -export { ReactantRouter as Router, RouterOptions, BaseReactantRouter }; +export { ReactantRouter as Router, RouterOptions }; diff --git a/packages/reactant-share/src/constants.ts b/packages/reactant-share/src/constants.ts index 8ad8df3c1..b8c9848bd 100644 --- a/packages/reactant-share/src/constants.ts +++ b/packages/reactant-share/src/constants.ts @@ -4,11 +4,10 @@ export const preloadedStateActionName = '@@reactant:preloadedState'; export const isClientName = '@@reactant:isClient'; export const loadFullStateActionName = '@@reactant:loadFullState'; export const syncRouterName = '@@reactant:syncRouter'; -export const syncRouterWorkerName = '@@reactant:syncRouterWorker'; // Server to Client export const proxyServerActionName = '@@reactant:proxyServer'; export const lastActionName = '@@reactant:lastAction'; -export const routerChangeName = '@@reactant:routerChange'; export const syncToClientsName = '@@reactant:syncToClients'; +export const syncWorkerRouterName = '@@reactant:syncWorkerRouter'; export const SharedAppOptions = Symbol('SharedAppOptions'); diff --git a/packages/reactant-share/src/interfaces.ts b/packages/reactant-share/src/interfaces.ts index bcd8b2fe6..a28527e49 100644 --- a/packages/reactant-share/src/interfaces.ts +++ b/packages/reactant-share/src/interfaces.ts @@ -1,7 +1,8 @@ +/* eslint-disable no-use-before-define */ import type { EmitParameter, Transport } from 'data-transport'; import type { Config as BaseConfig, App, Renderer } from 'reactant'; import type { ILastActionState } from 'reactant-last-action'; -import type { Router, RouterState } from 'reactant-router'; +import type { RouterState } from 'reactant-router'; import { isClientName, lastActionName, @@ -9,12 +10,10 @@ import { preloadedStateActionName, proxyClientActionName, proxyServerActionName, - routerChangeName, syncRouterName, - syncRouterWorkerName, + syncWorkerRouterName, syncToClientsName, } from './constants'; -import type { RouterChangeNameOptions } from './router'; export type { Transport } from 'data-transport'; @@ -72,6 +71,8 @@ export interface ISharedAppOptions { * Forced Sync for all client, enabled by default. * * If forcedSyncClient is false, then only the client's visibilityState is visible will the state be synchronized from server port. + * + * `forcedSyncClient` is only true in `SharedTab` type. */ forcedSyncClient?: boolean; } @@ -108,8 +109,7 @@ export interface ClientTransport { sequence: number ): Promise | null | undefined>; [isClientName](): Promise; - [syncRouterName](name: string): Promise; - [syncRouterWorkerName](router: Router['router'], name: string): void; + [syncRouterName](name: string, router?: RouterState): Promise; } export type ActionOptions = Pick< @@ -124,12 +124,10 @@ export interface ServerTransport { args: any[]; }): Promise; [lastActionName](options: ActionOptions): Promise; - [routerChangeName]( - options: RouterChangeNameOptions - ): Promise; [syncToClientsName]( options: Record | null | undefined ): Promise; + [syncWorkerRouterName](name: string): Promise; } export interface HandleServerOptions { @@ -156,13 +154,6 @@ export type FunctionKeys = Exclude< void >; -interface SpawnOptions { - /** - * Spawn transport, and default transport is client - */ - port?: Port; -} - export type ProxyExec = < T extends Record, K extends FunctionKeys, diff --git a/packages/reactant-share/src/portDetector.ts b/packages/reactant-share/src/portDetector.ts index 0e6e04ad5..3ed834371 100644 --- a/packages/reactant-share/src/portDetector.ts +++ b/packages/reactant-share/src/portDetector.ts @@ -181,6 +181,10 @@ export class PortDetector { return this.sharedAppOptions.type === 'SharedWorker'; } + get isServerWorker() { + return this.isWorkerMode && this.isServer; + } + get isServer() { return !!this.detectPort('server'); } diff --git a/packages/reactant-share/src/router.ts b/packages/reactant-share/src/router.ts index 79423f562..4defa91c2 100644 --- a/packages/reactant-share/src/router.ts +++ b/packages/reactant-share/src/router.ts @@ -1,19 +1,21 @@ -import { injectable, storeKey, inject, state, action, watch } from 'reactant'; -import { BaseReactantRouter, RouterOptions } from 'reactant-router'; +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable consistent-return */ +import { injectable, inject, watch } from 'reactant'; +import { Router as BaseReactantRouter, RouterOptions } from 'reactant-router'; import type { IRouterOptions as IBaseRouterOptions, RouterState, } from 'reactant-router'; import type { LocationState } from 'history'; import { - routerChangeName, SharedAppOptions, syncRouterName, - syncRouterWorkerName, + syncWorkerRouterName, } from './constants'; import type { ISharedAppOptions } from './interfaces'; import { PortDetector } from './portDetector'; import { spawn } from './spawn'; +import { fork } from './fork'; export { createBrowserHistory, @@ -21,34 +23,6 @@ export { createMemoryHistory, } from 'reactant-router'; -export type RouterChangeNameOptions = - | { - method: 'push'; - args: [string, LocationState?]; - currentName?: string; - } - | { - method: 'replace'; - args: [string, LocationState?]; - currentName?: string; - } - | { - method: 'go'; - args: [number]; - currentName?: string; - } - | { - method: 'goBack'; - args: []; - currentName?: string; - } - | { - method: 'goForward'; - args: []; - currentName?: string; - }; - -// eslint-disable-next-line @typescript-eslint/no-empty-interface export interface IRouterOptions extends IBaseRouterOptions { /** * default initial route @@ -82,135 +56,144 @@ class ReactantRouter extends BaseReactantRouter { ), }); - this.portDetector.onServer((transport) => - transport!.listen(syncRouterName, async (name) => this._routers[name]) - ); + // #region sync init router from clients in Worker mode + this.portDetector.onServer((transport) => { + if (this.portDetector.isWorkerMode) { + transport.emit(syncWorkerRouterName, this.name).then((router) => { + if (router) { + this._changeRoutingOnSever(this.name, router); + } + }); + } + }); this.portDetector.onClient((transport) => { - transport!.emit(syncRouterName, this.name).then((router) => { - if (!router) return; - this[storeKey]?.dispatch( - this.onLocationChanged(router.location, 'REPLACE')! - ); - }); + if (this.portDetector.isWorkerMode) { + return transport.listen(syncWorkerRouterName, async (name) => { + if (name === this.name) { + return this.router; + } + }); + } }); + // #endregion - this.portDetector.onServer((transport) => - transport.listen(syncRouterWorkerName, (router, name) => { - if (!this.router && router && name === this.name) { - this._setRouters(name, router); + // #region watch router and sync up router to all clients and server port + this.portDetector.onClient(() => { + return watch( + this, + () => this.router, + () => { + spawn(this as any, '_changeRoutingOnSever', [this.name, this.router]); } - }) - ); - this.portDetector.onClient((transport) => { - transport.emit( - { name: syncRouterWorkerName, respond: false }, - this._router, - this.name - ); - return transport.listen( - routerChangeName, - async ({ method, args = [], currentName }) => - new Promise((resolve) => { - if (currentName !== this.name) return; - if (this.portDetector.disableSyncClient) { - this.toBeRouted = () => { - const fn: Function = this.history[method]; - fn(...args); - // it ensure that the router is updated if all clients are hidden. - if ( - JSON.stringify(this._router) !== - JSON.stringify(this._routers[this.name]) - ) { - spawn(this as any, '_setRouters', [this.name, this._router]); - } - }; - return; - } - const stopWatching = watch( - this, - () => this._router, - () => { - stopWatching(); - resolve(this._router); - } - ); - const fn: Function = this.history[method]; - fn(...args); - }) ); }); - } - - private async _route({ method, args, currentName }: RouterChangeNameOptions) { - // support common SPA mode without any transports - if (!this.portDetector.transports.server) { - const fn: Function = this.history[method]; - fn(...args); - const stopWatching = watch( + this.portDetector.onServer(() => { + return watch( this, - () => this._router, + () => this.router, () => { - stopWatching(); - this._setRouters(currentName ?? this.name, this._router); + if (this.router) { + // just update the current router to routers mapping by name + this._setRouters(this.name, this.router); + } + if (!this.portDetector.isWorkerMode) { + fork(this as any, '_changeRoutingOnClient', [ + this.router, + this.name, + ]); + } } ); - return; - } - if (!this.portDetector.isWorkerMode) { - if (!currentName || currentName === this.name) { - const stopWatching = watch( - this, - () => this._router, - () => { - stopWatching(); - this._setRouters(currentName ?? this.name, this._router); - } - ); - const fn: Function = this.history[method]; - fn(...args); - } - } + }); + // #endregion - const routingPromise = this.portDetector.transports.server.emit( - routerChangeName, - { - method, - args, - currentName: currentName ?? this.name, - } as RouterChangeNameOptions - ); - // worker mode - if (this.portDetector.isWorkerMode) { - const router = await routingPromise; - if (router) { - this._setRouters(currentName ?? this.name, router); + // #region sync init router from server port in all modes + this.portDetector.onServer((transport) => { + return transport!.listen(syncRouterName, async (name, router) => { + const currentRouter = this._routers[name]!; + if (!currentRouter && router) { + this._changeRoutingOnSever(name, router); + } + return currentRouter; + }); + }); + this.portDetector.onClient((transport) => { + transport!.emit(syncRouterName, this.name, this.router).then((router) => { + if (!router) return; + this.history.push(router.location); + }); + }); + // #endregion + } + + protected _changeRoutingOnSever(name: string, router: RouterState) { + this._setRouters(name, router); + if (name === this.name) { + if (this.portDetector.isWorkerMode) { + this.dispatchChanged(router); + } else if ( + this.history.createHref(router.location) !== + this.history.createHref(this.router!.location) + ) { + this.history.push(router.location); } + fork(this as any, '_changeRoutingOnClient', [this.name, this.router]); + } else { + fork(this as any, '_changeRoutingOnClient', [name, router]); } + } - // non-worker mode and just route anther name nav from client - if ( - !this.portDetector.isWorkerMode && - currentName && - currentName !== this.name - ) { - const router = await routingPromise; - if (router) { - this._setRouters(currentName, router); + protected _changeRoutingOnClient(name: string, router: RouterState) { + if (name !== this.name) return; + const route = () => { + if ( + this.history && + this.history.createHref(router.location) !== + this.history.createHref(this.router!.location) + ) { + this.history.push(router.location); } + }; + if (this.portDetector.disableSyncClient) { + this.toBeRouted = route; + } else { + route(); } } - private get _router(): RouterState { - return this[storeKey]?.getState().router; + protected _makeRoutingOnClient({ + args, + action, + name, + }: { + args: any[]; + action: 'push' | 'replace' | 'go' | 'goBack' | 'goForward'; + name: string; + }) { + return new Promise((resolve) => { + const route = () => { + if (name === this.name) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + super[action](...args); + resolve(this.router); + } + }; + if (this.portDetector.disableSyncClient) { + this.toBeRouted = route; + } else { + route(); + } + }); } toBeRouted: (() => void) | null = null; - @state - private _routers: Record = {}; + protected _routers: Record = { + [this.name]: this.router, + }; - @action - private _setRouters(name: string, router: RouterState) { + protected _setRouters(name: string, router: RouterState) { this._routers[name] = router; } @@ -219,88 +202,119 @@ class ReactantRouter extends BaseReactantRouter { return this.options.defaultRoute ?? '/'; } - get currentPath(): string { - return this.router?.location.pathname ?? this.defaultRoute; - } - - get router(): RouterState { - return this._routers[this.name] ?? this._router; - } + protected defaultHistory = { + action: 'POP', + location: { + pathname: this.defaultRoute, + search: '', + hash: '', + state: undefined, + }, + }; - private async _push( - path: string, - locationState: LocationState, - name: string - ) { - await this._route({ - method: 'push', - args: [path, locationState], - currentName: name, - }); - } - - private async _replace( - path: string, - locationState: LocationState, - name: string - ) { - await this._route({ - method: 'replace', - args: [path, locationState], - currentName: name, - }); - } - - private async _go(n: number, name: string) { - await this._route({ - method: 'go', - args: [n], - currentName: name, - }); - } - - private async _goBack(name: string) { - await this._route({ - method: 'goBack', - args: [], - currentName: name, - }); + protected dispatchChanged(router: RouterState) { + this.store?.dispatch( + this.onLocationChanged(router.location, router.action)! + ); } - private async _goForward(name: string) { - await this._route({ - method: 'goForward', - args: [], - currentName: name, - }); + get currentPath() { + return this.router?.location.pathname ?? this.defaultRoute; } async push(path: string, locationState?: LocationState) { - await spawn(this as any, '_push', [path, locationState, this.clientName]); + if (this.portDetector.isServerWorker) { + const router: RouterState = await fork( + this as any, + '_makeRoutingOnClient', + [ + { + args: [path, locationState], + action: 'push', + name: this.name, + }, + ] + ); + this.dispatchChanged(router); + } else { + super.push(path, locationState); + } } async replace(path: string, locationState?: LocationState) { - await spawn(this as any, '_replace', [ - path, - locationState, - this.clientName, - ]); + if (this.portDetector.isServerWorker) { + const router: RouterState = await fork( + this as any, + '_makeRoutingOnClient', + [ + { + args: [path, locationState], + action: 'replace', + name: this.name, + }, + ] + ); + this.dispatchChanged(router); + } else { + super.replace(path, locationState); + } } async go(n: number) { - await spawn(this as any, '_go', [n, this.clientName]); + if (this.portDetector.isServerWorker) { + const router: RouterState = await fork( + this as any, + '_makeRoutingOnClient', + [ + { + args: [n], + action: 'go', + name: this.name, + }, + ] + ); + this.dispatchChanged(router); + } else { + super.go(n); + } } async goBack() { - await spawn(this as any, '_goBack', [this.clientName]); + if (this.portDetector.isServerWorker) { + const router: RouterState = await fork( + this as any, + '_makeRoutingOnClient', + [ + { + args: [], + action: 'goBack', + name: this.name, + }, + ] + ); + this.dispatchChanged(router); + } else { + super.goBack(); + } } async goForward() { - await spawn(this as any, '_goForward', [this.clientName]); - } - - get clientName() { - return this.portDetector.isClient ? this.name : null; + if (this.portDetector.isServerWorker) { + const router: RouterState = await fork( + this as any, + '_makeRoutingOnClient', + [ + { + args: [], + action: 'goForward', + name: this.name, + }, + ] + ); + this.dispatchChanged(router); + } else { + super.goForward(); + } } } diff --git a/packages/reactant-share/test/router.test.tsx b/packages/reactant-share/test/router.test.tsx index 348a071e1..37c24c8bb 100644 --- a/packages/reactant-share/test/router.test.tsx +++ b/packages/reactant-share/test/router.test.tsx @@ -1,6 +1,10 @@ +/* eslint-disable react/require-default-props */ +/* eslint-disable react/function-component-definition */ +/* eslint-disable no-promise-executor-return */ /* eslint-disable jsx-a11y/anchor-is-valid */ import React, { FunctionComponent } from 'react'; import { unmountComponentAtNode, render, Switch, Route } from 'reactant-web'; +import type { History, LocationListener } from 'history'; // eslint-disable-next-line import/no-extraneous-dependencies import { act } from 'react-dom/test-utils'; import { @@ -244,9 +248,9 @@ describe('base', () => { await new Promise((resolve) => setTimeout(resolve)); expect(onClientFn.mock.calls.length).toBe(1); - expect(subscribeOnClientFn.mock.calls.length).toBe(3); + expect(subscribeOnClientFn.mock.calls.length).toBe(1); expect(onServerFn.mock.calls.length).toBe(1); - expect(subscribeOnServerFn.mock.calls.length).toBe(3); + expect(subscribeOnServerFn.mock.calls.length).toBe(2); expect(serverContainer.querySelector('#content')?.textContent).toBe('0+'); // expect(clientContainer.querySelector('#content')?.textContent).toBe('0+'); act(() => { @@ -258,9 +262,9 @@ describe('base', () => { await new Promise((resolve) => setTimeout(resolve)); expect(onClientFn.mock.calls.length).toBe(1); - expect(subscribeOnClientFn.mock.calls.length).toBe(5); + expect(subscribeOnClientFn.mock.calls.length).toBe(2); expect(onServerFn.mock.calls.length).toBe(1); - expect(subscribeOnServerFn.mock.calls.length).toBe(5); + expect(subscribeOnServerFn.mock.calls.length).toBe(2); expect(serverContainer.querySelector('#content')?.textContent).toBe('0+'); expect(clientContainer.querySelector('#content')?.textContent).toBe('0+'); @@ -272,6 +276,103 @@ describe('base', () => { }); expect(clientContainer.querySelector('#content')?.textContent).toBe('home'); }); + + test('base server/client port mode with router with go/back button', async () => { + onClientFn = jest.fn(); + subscribeOnClientFn = jest.fn(); + onServerFn = jest.fn(); + subscribeOnServerFn = jest.fn(); + + const transports = mockPairTransports(); + // TODO: Error: Not implemented: navigation https://github.com/jsdom/jsdom/issues/2112 + const historyListeners = new Set(); + const MockRouterHistory = { + provide: 'MockRouterHistory', + useFactory: (router: Router) => { + // eslint-disable-next-line prefer-destructuring + const history: History = (router as any).history; + const { listen } = history; + history.listen = (callback) => { + historyListeners.add(callback); + return listen.call(history, (_location, _action) => { + callback(_location, _action); + }); + }; + }, + deps: [Router], + }; + + const serverApp = await createSharedApp({ + modules: [ + Router, + { + provide: RouterOptions, + useValue: { + createHistory: () => createBrowserHistory(), + } as IRouterOptions, + }, + ], + main: AppView, + render, + share: { + name: 'counter', + type: 'Base', + port: 'server', + transports: { + server: transports[0], + }, + }, + }); + + await serverApp.bootstrap(serverContainer); + + const clientApp = await createSharedApp({ + modules: [ + Router, + { + provide: RouterOptions, + useValue: { + createHistory: () => createBrowserHistory(), + } as IRouterOptions, + }, + MockRouterHistory, + ], + main: AppView, + render, + share: { + name: 'counter', + type: 'Base', + port: 'client', + transports: { + client: transports[1], + }, + }, + }); + + await clientApp.bootstrap(clientContainer); + + expect(clientApp.instance.router.currentPath).toBe('/'); + expect(serverApp.instance.router.currentPath).toBe('/'); + + act(() => { + clientContainer + .querySelector('#counter')! + .dispatchEvent(new MouseEvent('click', { bubbles: true })); + }); + + await new Promise((resolve) => setTimeout(resolve)); + expect(clientApp.instance.router.currentPath).toBe('/counter'); + expect(serverApp.instance.router.currentPath).toBe('/counter'); + // mock go back + for (const callback of historyListeners) { + callback( + { pathname: '/', search: '', hash: '', state: undefined }, + 'POP' + ); + } + expect(clientApp.instance.router.currentPath).toBe('/'); + expect(serverApp.instance.router.currentPath).toBe('/'); + }); }); describe('SharedWorker', () => { @@ -488,7 +589,7 @@ describe('SharedWorker', () => { await new Promise((resolve) => setTimeout(resolve)); expect(onClientFn.mock.calls.length).toBe(1); - expect(subscribeOnClientFn.mock.calls.length).toBe(3); + expect(subscribeOnClientFn.mock.calls.length).toBe(2); expect(onServerFn.mock.calls.length).toBe(1); expect(subscribeOnServerFn.mock.calls.length).toBe(2); expect(serverApp.instance.router.currentPath).toBe('/counter'); @@ -741,7 +842,7 @@ describe('Worker', () => { await new Promise((resolve) => setTimeout(resolve)); expect(onClientFn.mock.calls.length).toBe(1); - expect(subscribeOnClientFn.mock.calls.length).toBe(3); + expect(subscribeOnClientFn.mock.calls.length).toBe(2); expect(onServerFn.mock.calls.length).toBe(1); expect(subscribeOnServerFn.mock.calls.length).toBe(2); expect(serverApp.instance.router.currentPath).toBe('/counter'); @@ -904,9 +1005,7 @@ describe('Worker', () => { document.dispatchEvent(new Event('visibilitychange')); await new Promise((resolve) => setTimeout(resolve)); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - expect(clientApp.instance.router._router.location.pathname).toBe( + expect(clientApp.instance.router.router!.location.pathname).toBe( '/counter' ); expect(clientApp.instance.router.currentPath).toBe('/counter');