Skip to content

Commit

Permalink
Merge pull request #203 from spierala/signal-store--common
Browse files Browse the repository at this point in the history
Signal store  common
  • Loading branch information
spierala authored Oct 24, 2023
2 parents ec6744d + ab70153 commit 61cb939
Show file tree
Hide file tree
Showing 107 changed files with 6,569 additions and 1,360 deletions.
2 changes: 1 addition & 1 deletion jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { getJestProjects } = require('@nx/jest');
import { getJestProjects } from '@nx/jest';

export default {
projects: getJestProjects(),
Expand Down
18 changes: 18 additions & 0 deletions libs/common/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
11 changes: 11 additions & 0 deletions libs/common/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# common

This library was generated with [Nx](https://nx.dev).

## Building

Run `nx build common` to build the library.

## Running unit tests

Run `nx test common` to execute the unit tests via [Jest](https://jestjs.io).
17 changes: 17 additions & 0 deletions libs/common/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* eslint-disable */
export default {
displayName: 'common',
preset: '../../jest.preset.js',
globals: {},
transform: {
'^.+\\.[tj]s$': [
'ts-jest',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
},
],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/libs/common',
testRunner: 'jest-jasmine2',
};
8 changes: 8 additions & 0 deletions libs/common/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "@mini-rx/common",
"version": "0.0.2",
"peerDependencies": {
"rxjs": "^6.4.0 || ^7.0.0"
},
"sideEffects": false
}
56 changes: 56 additions & 0 deletions libs/common/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"name": "common",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/common/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nx/rollup:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/common",
"main": "libs/common/src/index.ts",
"tsConfig": "libs/common/tsconfig.lib.json",
"assets": [],
"project": "libs/common/package.json",
"format": ["esm", "cjs"],
"globals": [
{
"global": "Rx",
"moduleId": "rxjs"
},
{
"global": "Rx",
"moduleId": "rxjs/operators"
}
]
}
},
"publish": {
"command": "node tools/scripts/publish.mjs common {args.ver} {args.tag}",
"dependsOn": ["build"]
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/common/**/*.ts"]
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/common/jest.config.ts",
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
}
}
},
"tags": []
}
52 changes: 52 additions & 0 deletions libs/common/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
export { createMiniRxActionType, OperationType } from './lib/create-mini-rx-action-type';
export { combineMetaReducers } from './lib/combine-meta-reducers';
export { miniRxError } from './lib/mini-rx-error';
export { sortExtensions } from './lib/sort-extensions';

export { mapResponse } from './lib/map-response';
export { tapResponse } from './lib/tap-response';
export { ofType } from './lib/of-type';
export { createActionsOnQueue } from './lib/actions-on-queue';
export { defaultEffectsErrorHandler } from './lib/default-effects-error-handler';
export {
createRxEffect,
hasEffectMetaData,
HasEffectMetadata,
EffectConfig,
EFFECT_METADATA_KEY,
} from './lib/create-rx-effect';

export { LoggerExtension } from './lib/extensions/logger/logger.extension';
export { UndoExtension } from './lib/extensions/undo/undo-extension';
export { undo } from './lib/extensions/undo/undo';
export { ImmutableStateExtension } from './lib/extensions/immutable-state/immutable-state.extension';
export {
AbstractReduxDevtoolsExtension,
ReduxDevtoolsOptions,
} from './lib/extensions/redux-devtools/abstract-redux-devtools-extension';

export { createFeatureStoreReducer } from './lib/create-feature-store-reducer';
export { createComponentStoreReducer } from './lib/create-component-store-reducer';
export { generateId } from './lib/generate-id';
export { calculateExtensions } from './lib/calculate-extensions';
export { createReducerManager, ReducerManager } from './lib/reducer-manager';
export { componentStoreConfig } from './lib/component-store-config';

export { ExtensionId } from './lib/enums';
export {
Action,
Actions,
FeatureStoreConfig,
Reducer,
StateOrCallback,
MiniRxAction,
StoreExtension,
StoreConfig,
FeatureConfig,
MetaReducer,
ComponentStoreConfig,
ComponentStoreExtension,
AppState,
ReducerDictionary,
ReducerState,
} from './lib/models';
34 changes: 34 additions & 0 deletions libs/common/src/lib/actions-on-queue.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { createActionsOnQueue } from './actions-on-queue';
import { take } from 'rxjs';

describe('ActionsOnQueue', () => {
it('should dispatch actions', () => {
const spy = jest.fn();

const actionsOnQueue = createActionsOnQueue();
actionsOnQueue.actions$.subscribe(spy);

const action = { type: 'someAction' };
actionsOnQueue.dispatch(action);

expect(spy).toHaveBeenCalledWith(action);
}),
it('should queue actions', () => {
// Without queueScheduler this test would fail because of stack overflow (Read more here: https://blog.cloudboost.io/so-how-does-rx-js-queuescheduler-actually-work-188c1b46526e)

const callLimit = 5000;

const actionsOnQueue = createActionsOnQueue();

const spy = jest.fn().mockImplementation(() => {
// Every received action dispatches another action
actionsOnQueue.dispatch({ type: 'someAction' });
});

actionsOnQueue.actions$.pipe(take(callLimit)).subscribe(spy);

actionsOnQueue.dispatch({ type: 'someInitialAction' }); // Dispatch an action to start the whole thing

expect(spy).toHaveBeenCalledTimes(callLimit);
});
});
15 changes: 15 additions & 0 deletions libs/common/src/lib/actions-on-queue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { queueScheduler, Subject, observeOn } from 'rxjs';
import { Action } from './models';

export function createActionsOnQueue() {
const actionsSource = new Subject<Action>();

return {
actions$: actionsSource.asObservable().pipe(
observeOn(queueScheduler) // Prevent stack overflow: https://blog.cloudboost.io/so-how-does-rx-js-queuescheduler-actually-work-188c1b46526e
),
dispatch: (action: Action) => {
actionsSource.next(action);
},
};
}
33 changes: 33 additions & 0 deletions libs/common/src/lib/beautify-action.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { beautifyAction } from './beautify-action';
import { MiniRxAction } from './models';
import { createMiniRxActionType, OperationType } from './create-mini-rx-action-type';

describe('beautifyAction', () => {
it('should remove extra fields from FeatureStore/ComponentStore Actions', () => {
const csAction: MiniRxAction<any> = {
type: createMiniRxActionType(OperationType.SET_STATE, 'todos'),
stateOrCallback: {},
};

expect(beautifyAction(csAction)).toEqual({
type: '@mini-rx/todos/set-state',
payload: {},
});

const fsAction: MiniRxAction<any> = {
type: createMiniRxActionType(OperationType.SET_STATE, 'todos'),
stateOrCallback: {},
featureId: 'someFeatureId',
};

expect(beautifyAction(fsAction)).toEqual({
type: '@mini-rx/todos/set-state',
payload: {},
});
}),
it('should not touch other normal Actions', () => {
const action = { type: 'someAction' };

expect(beautifyAction(action)).toBe(action);
});
});
13 changes: 13 additions & 0 deletions libs/common/src/lib/beautify-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Action, MiniRxAction } from '../lib/models';
import { isMiniRxAction } from './is-mini-rx-action';

// Only display type and payload in the LoggingExtension and Redux DevTools
export function beautifyAction(action: Action | MiniRxAction<any>): Action {
if (isMiniRxAction(action)) {
return {
type: action.type,
payload: action.stateOrCallback,
};
}
return action;
}
13 changes: 13 additions & 0 deletions libs/common/src/lib/calc-next-state.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { calcNextState } from './calc-next-state';

describe('calcNextState', () => {
it('should calculate next state', () => {
const currentState = { count: 1 };
expect(calcNextState(currentState, { count: 2 })).toEqual({ count: 2 });

// Witch callback
expect(calcNextState(currentState, (state) => ({ count: state.count + 2 }))).toEqual({
count: 3,
});
});
});
10 changes: 10 additions & 0 deletions libs/common/src/lib/calc-next-state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { StateOrCallback } from './models';

export function calcNextState<T>(state: T, stateOrCallback: StateOrCallback<T>): T {
const newPartialState =
typeof stateOrCallback === 'function' ? stateOrCallback(state) : stateOrCallback;
return {
...state,
...newPartialState,
};
}
86 changes: 86 additions & 0 deletions libs/common/src/lib/calculate-extensions.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { ComponentStoreExtension, MetaReducer } from './models';
import { ExtensionId } from './enums';
import { calculateExtensions } from './calculate-extensions';

class MockLoggerExtension implements ComponentStoreExtension {
id = ExtensionId.LOGGER;
sortOrder = 1;
hasCsSupport = true as const;

init(): MetaReducer<any> {
return (v) => v;
}
}

class MockUndoExtension implements ComponentStoreExtension {
id = ExtensionId.UNDO;
sortOrder = 2;
hasCsSupport = true as const;

init(): MetaReducer<any> {
return (v) => v;
}
}

class MockImmutableStateExtension implements ComponentStoreExtension {
id = ExtensionId.IMMUTABLE_STATE;
sortOrder = 3;
hasCsSupport = true as const;

init(): MetaReducer<any> {
return (v) => v;
}
}

describe('calculateExtensions', () => {
it('should add local extensions', () => {
const extensions = [new MockLoggerExtension(), new MockUndoExtension()];
expect(calculateExtensions({ extensions })).toStrictEqual(extensions);
});

it('should add global extensions', () => {
const extensions = [new MockLoggerExtension(), new MockUndoExtension()];
expect(calculateExtensions(undefined, { extensions })).toStrictEqual(extensions);
});

it('should merge local and global extensions', () => {
const localExtensions = [new MockLoggerExtension()];
const globalExtensions = [new MockUndoExtension(), new MockImmutableStateExtension()];

const extensions = calculateExtensions(
{ extensions: localExtensions },
{ extensions: globalExtensions }
);

expect(extensions[0]).toBe(localExtensions[0]);
expect(extensions[1]).toBe(globalExtensions[0]);
expect(extensions[2]).toBe(globalExtensions[1]);
});

it('should merge local and global extensions (use local if extension is used globally and locally)', () => {
const localExtensions = [new MockLoggerExtension()];
const globalExtensions = [new MockLoggerExtension(), new MockImmutableStateExtension()];

const extensions = calculateExtensions(
{ extensions: localExtensions },
{ extensions: globalExtensions }
);

expect(extensions[0]).toBe(localExtensions[0]);
expect(extensions[1]).toBe(globalExtensions[1]);
});

it('should return empty extensions if no global or local extensions are defined', () => {
const extensions = calculateExtensions();
expect(extensions).toEqual([]);
});

it('should sort extensions', () => {
const extensions = [new MockImmutableStateExtension(), new MockLoggerExtension()];

const extensionsSorted = calculateExtensions({ extensions });

expect(extensionsSorted[0]).toBe(extensions[1]);
expect(extensionsSorted[1]).toBe(extensions[0]);
});
});
Loading

0 comments on commit 61cb939

Please sign in to comment.