mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[State Management][Docs] State syncing utils docs (#56479)
This commit is contained in:
parent
ea84cbf11c
commit
c001014f78
13 changed files with 562 additions and 8 deletions
|
@ -2,4 +2,5 @@
|
|||
|
||||
Utilities for building Kibana plugins.
|
||||
|
||||
- [State containers](./docs/state_containers/README.md).
|
||||
- [State containers](./docs/state_containers).
|
||||
- [State syncing utilities](./docs/state_sync).
|
||||
|
|
|
@ -12,7 +12,6 @@ your services or apps.
|
|||
container you can always access the latest state snapshot synchronously.
|
||||
- Unlike Redux, state containers are less verbose, see example below.
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
```ts
|
||||
|
@ -21,11 +20,11 @@ import { createStateContainer } from 'src/plugins/kibana_utils';
|
|||
const container = createStateContainer(
|
||||
{ count: 0 },
|
||||
{
|
||||
increment: (state: {count: number}) => (by: number) => ({ count: state.count + by }),
|
||||
double: (state: {count: number}) => () => ({ count: state.count * 2 }),
|
||||
increment: (state: { count: number }) => (by: number) => ({ count: state.count + by }),
|
||||
double: (state: { count: number }) => () => ({ count: state.count * 2 }),
|
||||
},
|
||||
{
|
||||
count: (state: {count: number}) => () => state.count,
|
||||
count: (state: { count: number }) => () => state.count,
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -35,7 +34,6 @@ container.transitions.double();
|
|||
console.log(container.selectors.count()); // 10
|
||||
```
|
||||
|
||||
|
||||
## Demos
|
||||
|
||||
See demos [here](../../demos/state_containers/).
|
||||
|
@ -47,11 +45,11 @@ npx -q ts-node src/plugins/kibana_utils/demos/state_containers/counter.ts
|
|||
npx -q ts-node src/plugins/kibana_utils/demos/state_containers/todomvc.ts
|
||||
```
|
||||
|
||||
|
||||
## Reference
|
||||
|
||||
- [Creating a state container](./creation.md).
|
||||
- [State transitions](./transitions.md).
|
||||
- [Using with React](./react.md).
|
||||
- [Using without React`](./no_react.md).
|
||||
- [Using without React](./no_react.md).
|
||||
- [Parallels with Redux](./redux.md).
|
||||
- [Syncing state with URL or SessionStorage](../state_sync)
|
||||
|
|
60
src/plugins/kibana_utils/docs/state_sync/README.md
Normal file
60
src/plugins/kibana_utils/docs/state_sync/README.md
Normal file
|
@ -0,0 +1,60 @@
|
|||
# State Syncing Utilities
|
||||
|
||||
State syncing utilities are a set of helpers for syncing your application state
|
||||
with URL or browser storage.
|
||||
|
||||
They are designed to work together with [state containers](../state_containers). But state containers are not required.
|
||||
|
||||
State syncing utilities include:
|
||||
|
||||
- `syncState` util which:
|
||||
- Subscribes to state changes and pushes them to state storage.
|
||||
- Optionally subscribes to state storage changes and pushes them to state.
|
||||
- Two types of storage compatible with `syncState`:
|
||||
- [KbnUrlStateStorage](./storages/kbn_url_storage.md) - Serializes state and persists it to URL's query param in rison or hashed format.
|
||||
Listens for state updates in the URL and pushes them back to state.
|
||||
- [SessionStorageStateStorage](./storages/session_storage.md) - Serializes state and persists it to session storage.
|
||||
|
||||
## Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
createStateContainer,
|
||||
syncState,
|
||||
createKbnUrlStateStorage,
|
||||
} from 'src/plugins/kibana_utils/public';
|
||||
|
||||
const stateContainer = createStateContainer({ count: 0 });
|
||||
const stateStorage = createKbnUrlStateStorage();
|
||||
|
||||
const { start, stop } = syncState({
|
||||
storageKey: '_a',
|
||||
stateContainer,
|
||||
stateStorage,
|
||||
});
|
||||
|
||||
start();
|
||||
|
||||
// state container change is synched to state storage
|
||||
// kbnUrlStateStorage updates the URL to "/#?_a=(count:2)"
|
||||
stateContainer.set({ count: 2 });
|
||||
|
||||
stop();
|
||||
```
|
||||
|
||||
## Demos Plugins
|
||||
|
||||
See demos plugins [here](../../../../../examples/state_containers_examples).
|
||||
|
||||
To run them, start kibana with `--run-examples` flag.
|
||||
|
||||
## Reference
|
||||
|
||||
- [Syncing state with URL](./storages/kbn_url_storage.md).
|
||||
- [Syncing state with sessionStorage](./storages/session_storage.md).
|
||||
- [Setting up initial state](./initial_state.md).
|
||||
- [Using without state containers](./no_state_containers.md).
|
||||
- [Handling empty or incomplete incoming state](./empty_or_incomplete_incoming_state.md).
|
||||
- [On-the-fly state migrations](./on_fly_state_migrations.md).
|
||||
- [syncStates helper](./sync_states.md).
|
||||
- [Helpers for Data plugin (syncing TimeRange, RefreshInterval and Filters)](./data_plugin_helpers.md).
|
|
@ -0,0 +1,7 @@
|
|||
# Helpers for syncing state with data plugins (TimeRange, RefreshInterval and Filters)
|
||||
|
||||
```ts
|
||||
// TODO: Waiting for
|
||||
// https://github.com/elastic/kibana/issues/55977
|
||||
// https://github.com/elastic/kibana/pull/56128
|
||||
```
|
|
@ -0,0 +1,54 @@
|
|||
# Handling empty or incomplete incoming state
|
||||
|
||||
Users have direct access to storages where we sync our state to.
|
||||
For example, in the URL, a user can manually change the URL and remove or corrupt important data which we expect to be there.
|
||||
URLs may also be programmatically generated, increasing the risk for mistakes which application can't handle.
|
||||
|
||||
`syncState` doesn't handle such edge cases passing input from storage to the state container as is.
|
||||
It is up to the application to handle such scenarios.
|
||||
|
||||
Consider the following example:
|
||||
|
||||
```ts
|
||||
// window.location.href is "/#?_a=(count:0"
|
||||
const defaultState = { count: 0 }; // default application state
|
||||
|
||||
const stateContainer = createStateContainer(defaultState);
|
||||
const stateStorage = createKbnUrlStateStorage();
|
||||
|
||||
const { start, stop } = syncState({
|
||||
storageKey: '_a',
|
||||
stateContainer,
|
||||
stateStorage,
|
||||
});
|
||||
|
||||
start();
|
||||
|
||||
// At this point, state and storage are in sync
|
||||
// state: {count: 0}
|
||||
// storage: {count: 0}
|
||||
|
||||
// Now user changes the URL manually to "/#?_a=(corrupt:0)",
|
||||
// triggering a state update with {corrupt: 0}
|
||||
```
|
||||
|
||||
The application could, for example, handle this gracefully, by using simple composition during setup:
|
||||
|
||||
```ts
|
||||
const { start, stop } = syncState({
|
||||
storageKey: '_a',
|
||||
stateContainer: {
|
||||
...stateContainer,
|
||||
set: state => stateContainer.set({ ...defaultState, ...state }),
|
||||
},
|
||||
stateStorage,
|
||||
});
|
||||
```
|
||||
|
||||
In this case, the corrupt value will not get into state, preventing misshaped state.
|
||||
|
||||
To help application developers remember such edge cases,
|
||||
`syncState` util sets a constraint,
|
||||
that setter to state container should be able to handle `null` value (see [IStateSyncConfig](../../public/state_sync/types.ts)).
|
||||
Incoming `null` value from state storage usually means that state is empty (e.g. URL without `storageKey` query param).
|
||||
So when using `syncState` util applications are required to at least handle incoming `null`.
|
71
src/plugins/kibana_utils/docs/state_sync/initial_state.md
Normal file
71
src/plugins/kibana_utils/docs/state_sync/initial_state.md
Normal file
|
@ -0,0 +1,71 @@
|
|||
# Setting up initial state
|
||||
|
||||
The `syncState` util doesn't do any initial state syncing between state and storage.
|
||||
Consider the following scenario:
|
||||
|
||||
```ts
|
||||
// window.location.href is "/#?_a=(count:2)"
|
||||
const defaultState = { count: 0 }; // default application state
|
||||
|
||||
const stateContainer = createStateContainer(defaultState);
|
||||
const stateStorage = createKbnUrlStateStorage();
|
||||
|
||||
const { start, stop } = syncState({
|
||||
storageKey: '_a',
|
||||
stateContainer,
|
||||
stateStorage,
|
||||
});
|
||||
|
||||
start();
|
||||
|
||||
// Now the storage and state are out of sync
|
||||
// state: {count: 0}
|
||||
// storage: {count: 2}
|
||||
```
|
||||
|
||||
It is up to the application to decide, how initial state should be synced and which state should take precedence, depending on the specific use case.
|
||||
|
||||
Questions to consider:
|
||||
|
||||
1. Should default state take precedence over URL?
|
||||
2. Should URL take precedence?
|
||||
3. Do we have to do any state migrations for what is coming from the URL?
|
||||
4. If URL doesn't have the whole state, should we merge it with default one or leave it behind?
|
||||
5. Is there any other state loading in parallel (e.g. from a `SavedObject`)? How should we merge those?
|
||||
6. Are we storing the state both in the URL and in the `sessionStorage`? Which one should take precedence and in which case?
|
||||
|
||||
A possible synchronization for the state conflict above could look like this:
|
||||
|
||||
```ts
|
||||
// window.location.href is "/#?_a=(count:2)"
|
||||
const defaultState = { count: 0 }; // default application state
|
||||
|
||||
const urlStateStorage = createKbnUrlStateStorage();
|
||||
|
||||
const initialStateFromUrl = urlStateStorage.get('_a');
|
||||
|
||||
// merge the default state and initial state from the url and use it as initial application state
|
||||
const initialState = {
|
||||
...defaultState,
|
||||
...initialStateFromUrl,
|
||||
};
|
||||
|
||||
const stateContainer = createStateContainer(initialState);
|
||||
|
||||
// preserve initial application state in the URL
|
||||
if (!initialStateFromUrl) {
|
||||
urlStateStorage.set('_a', initialState, { replace: true });
|
||||
}
|
||||
|
||||
const { start, stop } = syncState({
|
||||
storageKey: '_a',
|
||||
stateContainer,
|
||||
stateStorage: urlStateStorage,
|
||||
});
|
||||
|
||||
start();
|
||||
|
||||
// Now the storage and state are in sync
|
||||
// state: {count: 2}
|
||||
// storage: {count: 2}
|
||||
```
|
|
@ -0,0 +1,51 @@
|
|||
# Using state syncing utilities without state containers
|
||||
|
||||
It is possible to use `syncState` utility even if your app is not using [state containers](../state_containers).
|
||||
The `state` which is passed into `syncState` function should implement this simple interface:
|
||||
|
||||
```ts
|
||||
export interface BaseStateContainer<State extends BaseState> {
|
||||
get: () => State;
|
||||
set: (state: State | null) => void;
|
||||
state$: Observable<State>;
|
||||
}
|
||||
```
|
||||
|
||||
For example, assuming you have a custom state manager, setting up syncing state with the URL could look something like this:
|
||||
|
||||
```ts
|
||||
// my_state_manager.ts
|
||||
import { Subject } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
export class MyStateManager {
|
||||
private count: number = 0;
|
||||
|
||||
updated$ = new Subject();
|
||||
|
||||
setCount(count: number) {
|
||||
if (this.count !== count) {
|
||||
this.count = count;
|
||||
this.updated$.next();
|
||||
}
|
||||
}
|
||||
|
||||
getCount() {
|
||||
return this.count;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// app.ts
|
||||
import { syncState, createKbnUrlStateStorage } from 'src/plugins/kibana_utils/public';
|
||||
import { MyStateManager } from './my_state_manager';
|
||||
|
||||
const myStateManager = new MyStateManager();
|
||||
|
||||
syncState({
|
||||
get: () => ({ count: myStateManager.getCount() }),
|
||||
set: state => state && myStateManager.setCount(state.count),
|
||||
state$: myStateManager.updated$.pipe(map(() => ({ count: myStateManager.getCount() }))),
|
||||
});
|
||||
```
|
|
@ -0,0 +1,48 @@
|
|||
# On-the-fly state migrations
|
||||
|
||||
When retrieving initial state from storage we shouldn't forget about possible outdated state.
|
||||
Consider the scenario, where user launches application from a bookmarked link, which contains outdated state.
|
||||
|
||||
Similar to [handling initial state](./initial_state.md) example, applications could handle migrations during Initialization.
|
||||
|
||||
```ts
|
||||
import { migrate } from '../app/state_helpers';
|
||||
const urlStateStorage = createKbnUrlStateStorage();
|
||||
const initialStateFromUrl = urlStateStorage.get('_a');
|
||||
|
||||
// merge default state and initial state and migrate it to the current version
|
||||
const initialState = migrate({
|
||||
...defaultState,
|
||||
...initialStateFromUrl,
|
||||
});
|
||||
|
||||
const stateContainer = createStateContainer(initialState);
|
||||
```
|
||||
|
||||
It is also possible to apply migrations for any incoming state, similar to [handling empty or incomplete state](./empty_or_incomplete_incoming_state.md).
|
||||
|
||||
Imagine an edge case scenario, where a user is working in your application, and then pastes an old link for the same application, containing older state with a different structure.
|
||||
Since no application remount will happen, we need to transition to a new state on-the-fly, by applying necessary migrations.
|
||||
|
||||
```ts
|
||||
import { migrate } from '../app/state_helpers';
|
||||
|
||||
const urlStateStorage = createKbnUrlStateStorage();
|
||||
const initialStateFromUrl = urlStateStorage.get('_a');
|
||||
|
||||
// merge default state and initial state and migrate them to current version if needed
|
||||
const initialState = migrate({
|
||||
...defaultState,
|
||||
...initialStateFromUrl,
|
||||
});
|
||||
|
||||
const stateContainer = createStateContainer(initialState);
|
||||
const { start, stop } = syncState({
|
||||
storageKey: '_a',
|
||||
stateContainer: {
|
||||
...stateContainer,
|
||||
set: state => stateContainer.set(migrate({ ...defaultState, ...state })),
|
||||
},
|
||||
stateStorage,
|
||||
});
|
||||
```
|
|
@ -0,0 +1,7 @@
|
|||
# State Storage
|
||||
|
||||
Two types of storage compatible with `syncState`:
|
||||
|
||||
- [KbnUrlStateStorage](./kbn_url_storage.md) - Serialises state and persists it to URL's query param in rison or hashed format (similar to what AppState & GlobalState did in legacy world).
|
||||
Listens for state updates in the URL and pushes updates back to state.
|
||||
- [SessionStorageStateStorage](./session_storage.md) - Serializes state and persists it to session storage.
|
|
@ -0,0 +1,162 @@
|
|||
# Kbn Url Storage
|
||||
|
||||
`KbnUrlStateStorage` is a state storage for `syncState` utility which:
|
||||
|
||||
- Keeps state in sync with the URL.
|
||||
- Serializes data and stores it in the URL in one of the supported formats:
|
||||
1. [Rison](https://github.com/w33ble/rison-node) encoded.
|
||||
2. Hashed URL: In URL we store only the hash from the serialized state, but the state itself is stored in `sessionStorage`.
|
||||
See kibana's advanced option for more context `state:storeInSessionStorage`
|
||||
- Takes care of listening to the URL updates and notifies state about the updates.
|
||||
- Takes care of batching URL updates to prevent redundant browser history records.
|
||||
|
||||
### Basic Example
|
||||
|
||||
With state sync utility:
|
||||
|
||||
```ts
|
||||
import {
|
||||
createStateContainer,
|
||||
syncState,
|
||||
createKbnUrlStateStorage,
|
||||
} from 'src/plugins/kibana_utils/public';
|
||||
|
||||
const stateContainer = createStateContainer({ count: 0 });
|
||||
const stateStorage = createKbnUrlStateStorage();
|
||||
|
||||
const { start, stop } = syncState({
|
||||
storageKey: '_a',
|
||||
stateContainer,
|
||||
stateStorage,
|
||||
});
|
||||
|
||||
start();
|
||||
|
||||
// state container change is synced to state storage
|
||||
// in this case, kbnUrlStateStorage updates the URL to "/#?_a=(count:2)"
|
||||
stateContainer.set({ count: 2 });
|
||||
|
||||
stop();
|
||||
```
|
||||
|
||||
Or just by itself:
|
||||
|
||||
```ts
|
||||
// assuming url is "/#?_a=(count:2)"
|
||||
const stateStorage = createKbnUrlStateStorage();
|
||||
|
||||
stateStorage.get('_a'); // returns {count: a}
|
||||
stateStorage.set('_a', { count: 0 }); // updates url to "/#?_a=(count:0)"
|
||||
```
|
||||
|
||||
### Setting URL format option
|
||||
|
||||
```ts
|
||||
const stateStorage = createKbnUrlStateStorage({
|
||||
useHash: core.uiSettings.get('state:storeInSessionStorage'), // put the complete encoded rison or just the hash into the URL
|
||||
});
|
||||
```
|
||||
|
||||
### Passing [history](https://github.com/ReactTraining/history) instance
|
||||
|
||||
Under the hood `kbnUrlStateStorage` uses [history](https://github.com/ReactTraining/history) for updating the URL and for listening to the URL updates.
|
||||
To prevent bugs caused by missing history updates, make sure your app uses one instance of history for URL changes which may interfere with each other.
|
||||
|
||||
For example, if you use `react-router`:
|
||||
|
||||
```tsx
|
||||
const App = props => {
|
||||
useEffect(() => {
|
||||
const stateStorage = createKbnUrlStateStorage({
|
||||
useHash: props.uiSettings.get('state:storeInSessionStorage'),
|
||||
history: props.history,
|
||||
});
|
||||
|
||||
//....
|
||||
});
|
||||
|
||||
return <Router history={props.history} />;
|
||||
};
|
||||
```
|
||||
|
||||
### Url updates batching
|
||||
|
||||
`kbnUrlStateStorage` batches synchronous URL updates. Consider the example.
|
||||
|
||||
```ts
|
||||
const urlStateStorage = createKbnUrlStateStorage();
|
||||
|
||||
urlStateStorage.set('_a', { state: 1 });
|
||||
urlStateStorage.set('_b', { state: 2 });
|
||||
|
||||
// URL hasn't been updated yet
|
||||
setTimeout(() => {
|
||||
// now URL is actually "/#?_a=(state:1)&_b=(state:2)"
|
||||
// and 2 updates were batched into 1 history.push() call
|
||||
}, 0);
|
||||
```
|
||||
|
||||
For cases, where granular control over URL updates is needed, `kbnUrlStateStorage` provides these advanced apis:
|
||||
|
||||
- `kbnUrlStateStorage.flush({replace: boolean})` - allows to synchronously apply any pending updates.
|
||||
`replace` option allows to use `history.replace()` instead of `history.push()`. Returned boolean indicates if any update happened
|
||||
- `kbnUrlStateStorage.cancel()` - cancels any pending updates
|
||||
|
||||
### Sharing one `kbnUrlStateStorage` instance
|
||||
|
||||
`kbnUrlStateStorage` is stateful, as it keeps track of pending updates.
|
||||
So if there are multiple state syncs happening in the same time, then one instance of `kbnUrlStateStorage` should be used to make sure, that the same update queue is used.
|
||||
Otherwise it could cause redundant browser history records.
|
||||
|
||||
```ts
|
||||
// wrong:
|
||||
|
||||
const { start, stop } = syncStates([
|
||||
{
|
||||
storageKey: '_a',
|
||||
stateContainerA,
|
||||
stateStorage: createKbnUrlStateStorage(),
|
||||
},
|
||||
{
|
||||
storageKey: '_b',
|
||||
stateContainerB,
|
||||
stateStorage: createKbnUrlStateStorage(),
|
||||
},
|
||||
]);
|
||||
|
||||
// better:
|
||||
|
||||
const kbnUrlStateStorage = createKbnUrlStateStorage();
|
||||
const { start, stop } = syncStates([
|
||||
{
|
||||
storageKey: '_a',
|
||||
stateContainerA,
|
||||
stateStorage: kbnUrlStateStorage,
|
||||
},
|
||||
{
|
||||
storageKey: '_b',
|
||||
stateContainerB,
|
||||
stateStorage: kbnUrlStateStorage,
|
||||
},
|
||||
]);
|
||||
|
||||
// the best:
|
||||
|
||||
import { createBrowserHistory } from 'history';
|
||||
const history = createBrowserHistory();
|
||||
const kbnUrlStateStorage = createKbnUrlStateStorage({ history });
|
||||
const { start, stop } = syncStates([
|
||||
{
|
||||
storageKey: '_a',
|
||||
stateContainerA,
|
||||
stateStorage: kbnUrlStateStorage,
|
||||
},
|
||||
{
|
||||
storageKey: '_b',
|
||||
stateContainerB,
|
||||
stateStorage: kbnUrlStateStorage,
|
||||
},
|
||||
]);
|
||||
|
||||
<Router history={history} />;
|
||||
```
|
|
@ -0,0 +1,39 @@
|
|||
# Session Storage
|
||||
|
||||
To sync state from state containers to `sessionStorage` use `createSessionStorageStateStorage`.
|
||||
|
||||
```ts
|
||||
import {
|
||||
createStateContainer,
|
||||
syncState,
|
||||
createSessionStorageStateStorage,
|
||||
} from 'src/plugins/kibana_utils/public';
|
||||
|
||||
const stateContainer = createStateContainer({ count: 0 });
|
||||
const stateStorage = createSessionStorageStateStorage();
|
||||
|
||||
const { start, stop } = syncState({
|
||||
storageKey: '_a',
|
||||
stateContainer,
|
||||
stateStorage,
|
||||
});
|
||||
|
||||
start();
|
||||
|
||||
// state container change is synced to state storage
|
||||
// in this case, stateStorage serialises state and stores it in `window.sessionStorage` by key `_a`
|
||||
stateContainer.set({ count: 2 });
|
||||
|
||||
stop();
|
||||
```
|
||||
|
||||
You can also use `createSessionStorageStateStorage` imperatively:
|
||||
|
||||
```ts
|
||||
const stateStorage = createSessionStorageStateStorage();
|
||||
|
||||
stateStorage.set('_a', { count: 2 });
|
||||
stateStorage.get('_a');
|
||||
```
|
||||
|
||||
**Note**: external changes to `sessionStorageStateStorage` or `window.sessionStorage` don't trigger state container updates.
|
34
src/plugins/kibana_utils/docs/state_sync/sync_states.md
Normal file
34
src/plugins/kibana_utils/docs/state_sync/sync_states.md
Normal file
|
@ -0,0 +1,34 @@
|
|||
# Sync states utility
|
||||
|
||||
In case you need to sync multiple states or one state to multiple storages, there is a handy util for that.
|
||||
It saves a bit of boilerplate by returning `start` and `stop` functions for all `syncState` configs at once.
|
||||
|
||||
```ts
|
||||
import {
|
||||
createStateContainer,
|
||||
syncStates,
|
||||
createKbnUrlStateStorage,
|
||||
createSessionStorageStateStorage,
|
||||
} from 'src/plugins/kibana_utils/public';
|
||||
|
||||
const stateContainer = createStateContainer({ count: 0 });
|
||||
const urlStateStorage = createKbnUrlStateStorage();
|
||||
const sessionStorageStateStorage = createSessionStorageStateStorage();
|
||||
|
||||
const { start, stop } = syncStates([
|
||||
{
|
||||
storageKey: '_a',
|
||||
stateContainer,
|
||||
stateStorage: urlStateStorage,
|
||||
},
|
||||
{
|
||||
storageKey: '_a',
|
||||
stateContainer,
|
||||
stateStorage: sessionStorageStateStorage,
|
||||
},
|
||||
]);
|
||||
|
||||
start(); // start syncing to all storages at once
|
||||
|
||||
stop(); // stop syncing to all storages at once
|
||||
```
|
|
@ -151,6 +151,28 @@ describe('state_sync', () => {
|
|||
|
||||
stop();
|
||||
});
|
||||
|
||||
it('storage change with incomplete or differently shaped object should notify state and set new object as is', () => {
|
||||
container.set({ todos: [{ completed: false, id: 1, text: 'changed' }] });
|
||||
const { stop, start } = syncStates([
|
||||
{
|
||||
stateContainer: container,
|
||||
storageKey: '_s',
|
||||
stateStorage: testStateStorage,
|
||||
},
|
||||
]);
|
||||
start();
|
||||
|
||||
const differentlyShapedObject = {
|
||||
different: 'test',
|
||||
};
|
||||
(testStateStorage.get as jest.Mock).mockImplementation(() => differentlyShapedObject);
|
||||
storageChange$.next(differentlyShapedObject as any);
|
||||
|
||||
expect(container.getState()).toStrictEqual(differentlyShapedObject);
|
||||
|
||||
stop();
|
||||
});
|
||||
});
|
||||
|
||||
describe('integration', () => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue