@sigi/react
Version:
React bindings for sigi framework
449 lines • 18.8 kB
JavaScript
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