UNPKG

@sigi/react

Version:

React bindings for sigi framework

449 lines 18.8 kB
import { __awaiter, __decorate, __metadata } from "tslib"; import { jsx as _jsx } from "react/jsx-runtime"; import '@abraham/reflection'; import { GLOBAL_KEY_SYMBOL, EffectModule, ImmerReducer, Module, Effect, Reducer } from '@sigi/core'; import { emitSSREffects, match } from '@sigi/ssr'; import { useEffect } from 'react'; import { renderToString } from 'react-dom/server'; import { render, act } from '@testing-library/react'; import { Observable, of, timer } from 'rxjs'; import { endWith, map, mergeMap, withLatestFrom } from 'rxjs/operators'; import { SSRContext, useModule } from '../index.browser'; import { CountModule, TipModule, Service, ServiceModule } from './__fixtures__'; let InfinityWaitModel = class InfinityWaitModel extends EffectModule { constructor() { super(...arguments); this.defaultState = { count: 0 }; } setCount(state, count) { state.count = count; } infinityWait(payload$) { return payload$.pipe(map(() => this.getActions().setCount(1))); } }; __decorate([ ImmerReducer(), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Number]), __metadata("design:returntype", void 0) ], InfinityWaitModel.prototype, "setCount", null); __decorate([ Effect({ ssr: true, }), __metadata("design:type", Function), __metadata("design:paramtypes", [Observable]), __metadata("design:returntype", Observable) ], InfinityWaitModel.prototype, "infinityWait", null); InfinityWaitModel = __decorate([ Module('InfinityWaitModel') ], InfinityWaitModel); const Component = () => { const [state, actions] = useModule(CountModule); useEffect(() => { actions.setName('new name'); }, [actions]); return _jsx("span", { children: state.count }); }; const MODULES = [CountModule, TipModule, ServiceModule, Service]; describe('SSR server', () => { it('should run ssr effects', () => __awaiter(void 0, void 0, void 0, function* () { const state = yield emitSSREffects({ url: 'name' }, [CountModule], { providers: MODULES, }).pendingState; const moduleState = state['dataToPersist']['CountModule']; expect(moduleState).not.toBe(undefined); expect(moduleState.count).toBe(1); expect(moduleState.name).toBe('name'); expect(state['dataToPersist']).toMatchSnapshot(); })); it('should skip effect if it returns SKIP_SYMBOL', () => __awaiter(void 0, void 0, void 0, function* () { const state = yield emitSSREffects({}, [CountModule], { providers: MODULES, }).pendingState; const moduleState = state['dataToPersist']['CountModule']; expect(moduleState.name).toBe(''); expect(state['dataToPersist']).toMatchSnapshot(); })); it('should return right state in hooks', () => __awaiter(void 0, void 0, void 0, function* () { const req = {}; const { pendingState, injector } = emitSSREffects(req, [CountModule], { providers: MODULES, }); yield pendingState; const html = renderToString(_jsx(SSRContext, { value: injector, children: _jsx(Component, {}) })); expect(html).toContain('<span>1</span>'); expect(html).toMatchSnapshot(); })); it('should restore state from global', () => { var _a; global[GLOBAL_KEY_SYMBOL] = { CountModule: { count: 101, name: '', }, }; const testRenderer = render(_jsx(Component, {})); act(() => { testRenderer.rerender(_jsx(Component, {})); }); expect((_a = testRenderer.container.querySelector('span')) === null || _a === void 0 ? void 0 : _a.textContent).toBe('101'); delete global[GLOBAL_KEY_SYMBOL]; testRenderer.unmount(); }); it('should timeout', () => __awaiter(void 0, void 0, void 0, function* () { const req = {}; return emitSSREffects(req, [CountModule], { providers: MODULES, timeout: 0 }).pendingState.catch((e) => { expect(e.message).toBe('Terminate timeout'); }); })); it('should timeout #2', () => __awaiter(void 0, void 0, void 0, function* () { const req = {}; const { pendingState } = emitSSREffects(req, [InfinityWaitModel, TipModule], { timeout: 2 / 1000, }); yield pendingState.catch((e) => { expect(e.message).toBe('Terminate timeout'); }); })); it('should resolve empty object if no modules provided', () => __awaiter(void 0, void 0, void 0, function* () { const req = {}; const state = yield emitSSREffects(req, [], { providers: MODULES }).pendingState; expect(state['dataToPersist']).toStrictEqual({}); })); it('should do nothing if Module contains no SSREffects', () => __awaiter(void 0, void 0, void 0, function* () { const req = {}; let WithoutSSRModule = class WithoutSSRModule extends EffectModule { constructor() { super(...arguments); this.defaultState = { count: 0, }; } set(state, payload) { state.count = payload; } addOne(payload$) { return payload$.pipe(withLatestFrom(this.state$), map(([, { count }]) => this.getActions().set(count + 1))); } }; __decorate([ ImmerReducer(), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Number]), __metadata("design:returntype", void 0) ], WithoutSSRModule.prototype, "set", null); __decorate([ Effect(), __metadata("design:type", Function), __metadata("design:paramtypes", [Observable]), __metadata("design:returntype", Observable) ], WithoutSSRModule.prototype, "addOne", null); WithoutSSRModule = __decorate([ Module('WithoutSSR') ], WithoutSSRModule); const state = yield emitSSREffects(req, [WithoutSSRModule], { providers: MODULES }).pendingState; expect(state['dataToPersist']).toStrictEqual({}); })); it('should throw error if runEffects error', () => __awaiter(void 0, void 0, void 0, function* () { const req = {}; const error = new TypeError('whatever'); let SSRErrorModule = class SSRErrorModule extends EffectModule { constructor() { super(...arguments); this.defaultState = { count: 0, }; } addOne(payload$) { return payload$.pipe(withLatestFrom(this.state$), map(() => { throw error; })); } }; __decorate([ Effect({ ssr: true }), __metadata("design:type", Function), __metadata("design:paramtypes", [Observable]), __metadata("design:returntype", Observable) ], SSRErrorModule.prototype, "addOne", null); SSRErrorModule = __decorate([ Module('ErrorModule') ], SSRErrorModule); try { yield emitSSREffects(req, [SSRErrorModule], { providers: MODULES }).pendingState; throw new TypeError('Unreachable code path'); } catch (e) { expect(e).toBe(error); } })); it('should throw error if reducer throw', () => __awaiter(void 0, void 0, void 0, function* () { const req = {}; const error = new TypeError('whatever'); let SSRErrorModule = class SSRErrorModule extends EffectModule { constructor() { super(...arguments); this.defaultState = { count: 0, }; } set() { throw error; } addOne(payload$) { return payload$.pipe(withLatestFrom(this.state$), map(() => this.getActions().set())); } }; __decorate([ Reducer(), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], SSRErrorModule.prototype, "set", null); __decorate([ Effect({ ssr: true }), __metadata("design:type", Function), __metadata("design:paramtypes", [Observable]), __metadata("design:returntype", Observable) ], SSRErrorModule.prototype, "addOne", null); SSRErrorModule = __decorate([ Module('ErrorReducerModule') ], SSRErrorModule); try { yield emitSSREffects(req, [SSRErrorModule], { providers: MODULES }).pendingState; throw new TypeError('Unreachable code path'); } catch (e) { expect(e).toBe(error); } })); it('should throw error if payloadGetter throw', () => __awaiter(void 0, void 0, void 0, function* () { const req = {}; const error = new TypeError('whatever'); let SSRErrorModule = class SSRErrorModule extends EffectModule { constructor() { super(...arguments); this.defaultState = { count: 0, }; } set(state, payload) { return Object.assign(Object.assign({}, state), { count: payload }); } addOne(payload$) { return payload$.pipe(withLatestFrom(this.state$), map(([, state]) => this.getActions().set(state.count + 1))); } }; __decorate([ Reducer(), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Number]), __metadata("design:returntype", void 0) ], SSRErrorModule.prototype, "set", null); __decorate([ Effect({ payloadGetter: () => { throw error; }, }), __metadata("design:type", Function), __metadata("design:paramtypes", [Observable]), __metadata("design:returntype", Observable) ], SSRErrorModule.prototype, "addOne", null); SSRErrorModule = __decorate([ Module('ErrorMiddlewareModule') ], SSRErrorModule); try { yield emitSSREffects(req, [SSRErrorModule], { providers: MODULES }).pendingState; throw new TypeError('Unreachable code path'); } catch (e) { expect(e).toBe(error); } })); it('should replace injector if providers provided', () => __awaiter(void 0, void 0, void 0, function* () { const req = {}; const state = yield emitSSREffects(req, [ServiceModule], { providers: MODULES }).pendingState; expect(state['dataToPersist'].ServiceModule.name).toBe('client service'); const state2 = yield emitSSREffects(req, [ServiceModule], { providers: [...MODULES, { provide: Service, useValue: { getName: () => of('server service') } }], }).pendingState; expect(state2['dataToPersist'].ServiceModule.name).toBe('server service'); })); it('should persist states which mutated by the other modules', () => __awaiter(void 0, void 0, void 0, function* () { let StateModel = class StateModel extends EffectModule { constructor() { super(...arguments); this.defaultState = { count: 0 }; } setCount(state, count) { state.count = count; } }; __decorate([ ImmerReducer(), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Number]), __metadata("design:returntype", void 0) ], StateModel.prototype, "setCount", null); StateModel = __decorate([ Module('InnerStateModule') ], StateModel); let CountModule = class CountModule extends EffectModule { constructor(stateModule) { super(); this.stateModule = stateModule; this.defaultState = {}; } getCount(payload$) { return payload$.pipe(mergeMap(() => timer(20).pipe(map(() => this.stateModule.getActions().setCount(1)), endWith(this.terminate())))); } }; __decorate([ Effect({ ssr: true, }), __metadata("design:type", Function), __metadata("design:paramtypes", [Observable]), __metadata("design:returntype", Observable) ], CountModule.prototype, "getCount", null); CountModule = __decorate([ Module('InnerCountModule'), __metadata("design:paramtypes", [StateModel]) ], CountModule); const req = {}; const state = yield emitSSREffects(req, [CountModule, StateModel]).pendingState; expect(state['dataToPersist']).toEqual({ InnerCountModule: {}, InnerStateModule: { count: 1, }, }); })); it('should persist actions to retry if needed', () => __awaiter(void 0, void 0, void 0, function* () { const req = { failure: true }; const state = yield emitSSREffects(req, [ServiceModule], { providers: MODULES }).pendingState; expect(state['actionsToRetry']).toEqual({ ServiceModule: ['setNameWithFailure'] }); })); it('should be able to persist actions to retry on the other module, and the skipped actions', () => __awaiter(void 0, void 0, void 0, function* () { let InnerServiceModule = class InnerServiceModule extends EffectModule { constructor(service) { super(); this.service = service; this.defaultState = { count: 0, name: '' }; } setName(state, name) { state.name = name; } setNameWithFailure(payload$) { return payload$.pipe(mergeMap(() => of(this.retryOnClient().setNameWithFailure(), this.terminate()))); } }; __decorate([ ImmerReducer(), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, String]), __metadata("design:returntype", void 0) ], InnerServiceModule.prototype, "setName", null); __decorate([ Effect(), __metadata("design:type", Function), __metadata("design:paramtypes", [Observable]), __metadata("design:returntype", Observable) ], InnerServiceModule.prototype, "setNameWithFailure", null); InnerServiceModule = __decorate([ Module('InnerServiceModule'), __metadata("design:paramtypes", [Service]) ], InnerServiceModule); let InnerCountModule2 = class InnerCountModule2 extends EffectModule { constructor(serviceModule) { super(); this.serviceModule = serviceModule; this.defaultState = {}; } setName(payload$) { return payload$.pipe(mergeMap(() => of(this.serviceModule.getActions().setNameWithFailure(1)))); } skippedSetName(payload$) { return payload$.pipe(map(() => this.serviceModule.getActions().setName('skipped'))); } }; __decorate([ Effect({ ssr: true, }), __metadata("design:type", Function), __metadata("design:paramtypes", [Observable]), __metadata("design:returntype", Observable) ], InnerCountModule2.prototype, "setName", null); __decorate([ Effect({ payloadGetter(_, skipAction) { return skipAction; }, }), __metadata("design:type", Function), __metadata("design:paramtypes", [Observable]), __metadata("design:returntype", Observable) ], InnerCountModule2.prototype, "skippedSetName", null); InnerCountModule2 = __decorate([ Module('InnerCountModule2'), __metadata("design:paramtypes", [InnerServiceModule]) ], InnerCountModule2); const req = {}; const state = yield emitSSREffects(req, [InnerServiceModule, InnerCountModule2], { providers: MODULES }) .pendingState; expect(state['actionsToRetry']).toEqual({ InnerCountModule2: ['skippedSetName'], InnerServiceModule: ['setNameWithFailure'], }); })); it('should support match fn', () => __awaiter(void 0, void 0, void 0, function* () { let InnerServiceModule2 = class InnerServiceModule2 extends EffectModule { constructor() { super(...arguments); this.defaultState = { count: 0, name: '' }; } setName(state, name) { state.name = name; } setNameWithFailure(payload$) { return payload$.pipe(mergeMap((l) => of(this.getActions().setName(`length: ${l}`), this.terminate()))); } }; __decorate([ ImmerReducer(), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, String]), __metadata("design:returntype", void 0) ], InnerServiceModule2.prototype, "setName", null); __decorate([ Effect({ payloadGetter: match(['/users/:id'], (ctx) => ctx.request.path)((ctx) => { return ctx.request.path.length; }), }), __metadata("design:type", Function), __metadata("design:paramtypes", [Observable]), __metadata("design:returntype", Observable) ], InnerServiceModule2.prototype, "setNameWithFailure", null); InnerServiceModule2 = __decorate([ Module('InnerServiceModule2') ], InnerServiceModule2); const req = { request: { path: '/users/linus', }, }; const state = yield emitSSREffects(req, [InnerServiceModule2], { providers: [InnerServiceModule2] }).pendingState; expect(state['dataToPersist']).toEqual({ InnerServiceModule2: { count: 0, name: `length: ${req.request.path.length}`, }, }); })); }); //# sourceMappingURL=ssr.server.spec.js.map