mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
refactor(store): merge getSnapshot/subscribe into attach (#364)
This commit is contained in:
@@ -31,40 +31,25 @@ export interface TaskContext<Target, State extends object> {
|
||||
}
|
||||
|
||||
// ----------------------------------------
|
||||
// Sync Config
|
||||
// Attach
|
||||
// ----------------------------------------
|
||||
|
||||
export type GetSnapshot<Target, State extends object> = (ctx: GetSnapshotContext<Target, State>) => Partial<State>;
|
||||
export type Attach<Target, State extends object> = (ctx: AttachContext<Target, State>) => void;
|
||||
|
||||
export interface GetSnapshotContext<Target, State extends object> {
|
||||
export interface AttachContext<Target, State extends object> {
|
||||
target: Target;
|
||||
get: () => Readonly<State>;
|
||||
initialState: Readonly<State>;
|
||||
}
|
||||
|
||||
export type Subscribe<Target, State extends object> = (ctx: SubscribeContext<Target, State>) => void;
|
||||
|
||||
export interface SubscribeContext<Target, State extends object> {
|
||||
target: Target;
|
||||
update: () => void;
|
||||
signal: AbortSignal;
|
||||
get: () => Readonly<State>;
|
||||
set: (partial: Partial<State>) => void;
|
||||
}
|
||||
|
||||
// ----------------------------------------
|
||||
// Feature Context
|
||||
// ----------------------------------------
|
||||
|
||||
export interface FeatureContext<Target, State extends object> {
|
||||
task: Task<Target, State>;
|
||||
get: () => Readonly<State>;
|
||||
target: () => Target;
|
||||
}
|
||||
|
||||
/** Context passed to state factory - uses loose types to enable State inference. */
|
||||
export interface StateFactoryContext<Target> {
|
||||
task: Task<Target, any>;
|
||||
get: () => Readonly<object>;
|
||||
target: () => Target;
|
||||
}
|
||||
|
||||
@@ -76,8 +61,7 @@ export type StateFactory<Target, State extends object> = (ctx: StateFactoryConte
|
||||
|
||||
export interface FeatureConfig<Target, State extends object> {
|
||||
state: StateFactory<Target, State>;
|
||||
getSnapshot: GetSnapshot<Target, State>;
|
||||
subscribe: Subscribe<Target, State>;
|
||||
attach?: Attach<Target, State>;
|
||||
}
|
||||
|
||||
export interface Feature<Target, State extends object> extends FeatureConfig<Target, State> {
|
||||
|
||||
@@ -6,28 +6,21 @@ export interface State<T extends object> {
|
||||
}
|
||||
|
||||
export interface WritableState<T extends object> extends State<T> {
|
||||
set: <K extends keyof T>(key: K, value: T[K]) => void;
|
||||
patch: (partial: Partial<T>) => void;
|
||||
delete: <K extends keyof T>(key: K) => void;
|
||||
}
|
||||
|
||||
let flushScheduled = false;
|
||||
|
||||
let isFlushScheduled = false;
|
||||
function scheduleFlush(): void {
|
||||
if (flushScheduled) return;
|
||||
flushScheduled = true;
|
||||
if (isFlushScheduled) return;
|
||||
isFlushScheduled = true;
|
||||
queueMicrotask(flush);
|
||||
}
|
||||
|
||||
const pendingContainers = new Set<StateContainer<any>>();
|
||||
|
||||
export function flush(): void {
|
||||
flushScheduled = false;
|
||||
|
||||
for (const container of pendingContainers) {
|
||||
container.flush();
|
||||
}
|
||||
|
||||
isFlushScheduled = false;
|
||||
for (const container of pendingContainers) container.flush();
|
||||
pendingContainers.clear();
|
||||
}
|
||||
|
||||
@@ -46,21 +39,9 @@ class StateContainer<T extends object> implements WritableState<T> {
|
||||
return this.#current;
|
||||
}
|
||||
|
||||
set<K extends keyof T>(key: K, value: T[K]): void {
|
||||
if (Object.is(this.#current[key], value)) return;
|
||||
this.#current = Object.freeze({ ...this.#current, [key]: value });
|
||||
this.#markPending();
|
||||
}
|
||||
|
||||
delete<K extends keyof T>(key: K): void {
|
||||
if (!(key in this.#current)) return;
|
||||
const { [key]: _, ...rest } = this.#current;
|
||||
this.#current = Object.freeze(rest as T);
|
||||
this.#markPending();
|
||||
}
|
||||
|
||||
patch(partial: Partial<T>): void {
|
||||
const next = { ...this.#current };
|
||||
|
||||
let changed = false;
|
||||
|
||||
for (const key in partial) {
|
||||
@@ -88,7 +69,6 @@ class StateContainer<T extends object> implements WritableState<T> {
|
||||
flush(): void {
|
||||
if (!this.#pending) return;
|
||||
this.#pending = false;
|
||||
|
||||
for (const fn of this.#listeners) fn();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { PendingTask, StoreConfig } from './config';
|
||||
import { StoreError } from './errors';
|
||||
import type {
|
||||
AnyFeature,
|
||||
AttachContext,
|
||||
StateFactoryContext,
|
||||
TaskContext,
|
||||
TaskHandler,
|
||||
@@ -37,16 +38,15 @@ export function createStore<Features extends AnyFeature[]>(config: StoreConfig<F
|
||||
// Reactive state - initialized after building features
|
||||
let state: WritableState<State>;
|
||||
|
||||
const ctx: StateFactoryContext<Target> = {
|
||||
const stateFactoryCtx: StateFactoryContext<Target> = {
|
||||
task: executeTask,
|
||||
get: () => state.current,
|
||||
target: () => {
|
||||
if (!target) throw new StoreError('NO_TARGET');
|
||||
return target;
|
||||
},
|
||||
};
|
||||
|
||||
const featureState = buildFeatureState(ctx);
|
||||
const featureState = buildFeatureState(stateFactoryCtx);
|
||||
state = createState(featureState);
|
||||
|
||||
const store = {
|
||||
@@ -110,23 +110,22 @@ export function createStore<Features extends AnyFeature[]>(config: StoreConfig<F
|
||||
attachAbort = new AbortController();
|
||||
const signal = attachAbort.signal;
|
||||
|
||||
state.patch(featureState);
|
||||
// Create attach context once, share across all features
|
||||
const attachCtx: AttachContext<Target, State> = {
|
||||
target: newTarget,
|
||||
signal,
|
||||
get: () => state.current,
|
||||
set: (partial) => state.patch(partial),
|
||||
};
|
||||
|
||||
for (const feature of features) {
|
||||
try {
|
||||
feature.subscribe({
|
||||
target: newTarget,
|
||||
update: () => syncFeature(feature, newTarget),
|
||||
signal,
|
||||
get: () => state.current,
|
||||
});
|
||||
feature.attach?.(attachCtx);
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
}
|
||||
}
|
||||
|
||||
syncAll();
|
||||
|
||||
try {
|
||||
config.onAttach?.({ store, target: newTarget, signal });
|
||||
} catch (error) {
|
||||
@@ -176,26 +175,6 @@ export function createStore<Features extends AnyFeature[]>(config: StoreConfig<F
|
||||
return result as State;
|
||||
}
|
||||
|
||||
function syncAll(): void {
|
||||
if (!target) return;
|
||||
for (const feature of features) {
|
||||
syncFeature(feature, target);
|
||||
}
|
||||
}
|
||||
|
||||
function syncFeature(feature: AnyFeature<Target>, t: Target): void {
|
||||
try {
|
||||
const snapshot = feature.getSnapshot({
|
||||
target: t,
|
||||
get: () => state.current,
|
||||
initialState: featureState,
|
||||
});
|
||||
state.patch(snapshot as Partial<State>);
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function executeTask<Output>(handler: TaskHandler<Target, State, Output>): Promise<Awaited<Output>>;
|
||||
async function executeTask<Output>(options: TaskOptions<Target, State, Output>): Promise<Awaited<Output>>;
|
||||
async function executeTask<Output>(
|
||||
|
||||
@@ -3,20 +3,15 @@ import { describe, expect, it, vi } from 'vitest';
|
||||
import { defineFeature, isFeature } from '../feature';
|
||||
|
||||
describe('defineFeature', () => {
|
||||
it('creates feature with create function and config', () => {
|
||||
it('creates feature with state factory and optional attach', () => {
|
||||
interface Target {
|
||||
value: number;
|
||||
}
|
||||
|
||||
const feature = defineFeature<Target>()({
|
||||
state: ({ task, target }) => ({
|
||||
// State
|
||||
state: ({ task }) => ({
|
||||
count: 0,
|
||||
// Actions
|
||||
increment(amount: number) {
|
||||
target().value += amount;
|
||||
},
|
||||
asyncIncrement(amount: number) {
|
||||
return task({
|
||||
key: 'increment',
|
||||
handler: ({ target }) => {
|
||||
@@ -25,16 +20,17 @@ describe('defineFeature', () => {
|
||||
});
|
||||
},
|
||||
}),
|
||||
getSnapshot: ({ target }) => ({ count: target.value }),
|
||||
subscribe: vi.fn(),
|
||||
|
||||
attach({ target, set }) {
|
||||
set({ count: target.value });
|
||||
},
|
||||
});
|
||||
|
||||
expect(feature.state).toBeTypeOf('function');
|
||||
expect(feature.getSnapshot).toBeTypeOf('function');
|
||||
expect(feature.subscribe).toBeTypeOf('function');
|
||||
expect(feature.attach).toBeTypeOf('function');
|
||||
});
|
||||
|
||||
it('factory receives task, get, and target helpers', () => {
|
||||
it('factory receives task and target helpers', () => {
|
||||
interface Target {
|
||||
value: number;
|
||||
}
|
||||
@@ -43,8 +39,6 @@ describe('defineFeature', () => {
|
||||
|
||||
defineFeature<Target>()({
|
||||
state: factorySpy,
|
||||
getSnapshot: () => ({ count: 0 }),
|
||||
subscribe: () => {},
|
||||
});
|
||||
|
||||
// Can't call the factory directly, but we can verify the shape
|
||||
@@ -52,20 +46,23 @@ describe('defineFeature', () => {
|
||||
expect(factorySpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('allows sync actions using target()', () => {
|
||||
it('allows sync actions using task handler', () => {
|
||||
interface Target {
|
||||
volume: number;
|
||||
}
|
||||
|
||||
const feature = defineFeature<Target>()({
|
||||
state: ({ target }) => ({
|
||||
state: ({ task }) => ({
|
||||
volume: 1,
|
||||
setVolume(value: number) {
|
||||
target().volume = value;
|
||||
return task({
|
||||
key: 'volume',
|
||||
handler: ({ target }) => {
|
||||
target.volume = value;
|
||||
},
|
||||
});
|
||||
},
|
||||
}),
|
||||
getSnapshot: ({ target }) => ({ volume: target.volume }),
|
||||
subscribe: () => {},
|
||||
});
|
||||
|
||||
expect(feature.state).toBeTypeOf('function');
|
||||
@@ -86,8 +83,6 @@ describe('defineFeature', () => {
|
||||
});
|
||||
},
|
||||
}),
|
||||
getSnapshot: () => ({ playing: false }),
|
||||
subscribe: () => {},
|
||||
});
|
||||
|
||||
expect(feature.state).toBeTypeOf('function');
|
||||
@@ -109,20 +104,25 @@ describe('defineFeature', () => {
|
||||
});
|
||||
},
|
||||
}),
|
||||
getSnapshot: () => ({ loading: false }),
|
||||
subscribe: () => {},
|
||||
});
|
||||
|
||||
expect(feature.state).toBeTypeOf('function');
|
||||
});
|
||||
|
||||
it('attach is optional', () => {
|
||||
const feature = defineFeature<HTMLVideoElement>()({
|
||||
state: () => ({ playing: false }),
|
||||
});
|
||||
|
||||
expect(feature.state).toBeTypeOf('function');
|
||||
expect(feature.attach).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isFeature', () => {
|
||||
it('returns true for features created with defineFeature', () => {
|
||||
const feature = defineFeature<HTMLVideoElement>()({
|
||||
state: () => ({ playing: false }),
|
||||
getSnapshot: () => ({ playing: false }),
|
||||
subscribe: () => {},
|
||||
});
|
||||
|
||||
expect(isFeature(feature)).toBe(true);
|
||||
|
||||
@@ -21,15 +21,16 @@ describe('store lifecycle integration', () => {
|
||||
});
|
||||
},
|
||||
}),
|
||||
getSnapshot: ({ target: t }) => ({ count: t.value }),
|
||||
subscribe: ({ target: t, update, signal }) => {
|
||||
events.push('subscribe');
|
||||
t.addEventListener('change', update, { signal });
|
||||
|
||||
attach({ target: t, signal, set }) {
|
||||
events.push('attach-feature');
|
||||
set({ count: t.value });
|
||||
|
||||
t.addEventListener('change', () => set({ count: t.value }), { signal });
|
||||
signal.addEventListener('abort', () => events.push('unsubscribe'));
|
||||
},
|
||||
});
|
||||
|
||||
// Cast to any for test access to dynamic properties
|
||||
const store = createStore({
|
||||
features: [feature],
|
||||
onSetup: () => events.push('setup'),
|
||||
@@ -42,7 +43,7 @@ describe('store lifecycle integration', () => {
|
||||
targetInstance.value = 5;
|
||||
const detach = store.attach(targetInstance);
|
||||
|
||||
expect(events).toEqual(['setup', 'subscribe', 'attach']);
|
||||
expect(events).toEqual(['setup', 'attach-feature', 'attach']);
|
||||
expect(store.state.count).toBe(5);
|
||||
|
||||
await store.increment();
|
||||
@@ -93,8 +94,6 @@ describe('task coordination', () => {
|
||||
});
|
||||
},
|
||||
}),
|
||||
getSnapshot: () => ({ loading: false }),
|
||||
subscribe: () => {},
|
||||
});
|
||||
|
||||
const store = createStore({
|
||||
@@ -134,8 +133,6 @@ describe('task coordination', () => {
|
||||
});
|
||||
},
|
||||
}),
|
||||
getSnapshot: () => ({ fetching: false }),
|
||||
subscribe: () => {},
|
||||
});
|
||||
|
||||
const store = createStore({ features: [feature] });
|
||||
@@ -173,8 +170,6 @@ describe('task coordination', () => {
|
||||
});
|
||||
},
|
||||
}),
|
||||
getSnapshot: () => ({ running: false }),
|
||||
subscribe: () => {},
|
||||
});
|
||||
|
||||
const store = createStore({
|
||||
@@ -218,8 +213,6 @@ describe('task coordination', () => {
|
||||
});
|
||||
},
|
||||
}),
|
||||
getSnapshot: () => ({ playing: false }),
|
||||
subscribe: () => {},
|
||||
});
|
||||
|
||||
const store = createStore({ features: [feature] });
|
||||
@@ -251,8 +244,6 @@ describe('task coordination', () => {
|
||||
});
|
||||
},
|
||||
}),
|
||||
getSnapshot: () => ({ playing: false }),
|
||||
subscribe: () => {},
|
||||
});
|
||||
|
||||
const store = createStore({
|
||||
@@ -287,8 +278,6 @@ describe('task coordination', () => {
|
||||
});
|
||||
},
|
||||
}),
|
||||
getSnapshot: () => ({ playing: false }),
|
||||
subscribe: () => {},
|
||||
});
|
||||
|
||||
const store = createStore({ features: [feature] });
|
||||
@@ -312,14 +301,18 @@ describe('state syncing', () => {
|
||||
it('multiple features merge state correctly', () => {
|
||||
const audioFeature = defineFeature<{ volume: number; rate: number }>()({
|
||||
state: () => ({ volume: 1 }),
|
||||
getSnapshot: ({ target }) => ({ volume: target.volume }),
|
||||
subscribe: () => {},
|
||||
|
||||
attach({ target, set }) {
|
||||
set({ volume: target.volume });
|
||||
},
|
||||
});
|
||||
|
||||
const playbackFeature = defineFeature<{ volume: number; rate: number }>()({
|
||||
state: () => ({ rate: 1 }),
|
||||
getSnapshot: ({ target }) => ({ rate: target.rate }),
|
||||
subscribe: () => {},
|
||||
|
||||
attach({ target, set }) {
|
||||
set({ rate: target.rate });
|
||||
},
|
||||
});
|
||||
|
||||
const store = createStore({
|
||||
@@ -355,9 +348,11 @@ describe('immediate execution', () => {
|
||||
});
|
||||
},
|
||||
}),
|
||||
getSnapshot: ({ target }) => ({ paused: target.paused }),
|
||||
subscribe: ({ target, update, signal }) => {
|
||||
target.addEventListener('play', update, { signal });
|
||||
|
||||
attach({ target, signal, set }) {
|
||||
set({ paused: target.paused });
|
||||
|
||||
target.addEventListener('play', () => set({ paused: target.paused }), { signal });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -390,8 +385,6 @@ describe('meta tracing', () => {
|
||||
});
|
||||
},
|
||||
}),
|
||||
getSnapshot: () => ({ playing: false }),
|
||||
subscribe: () => {},
|
||||
});
|
||||
|
||||
const store = createStore({ features: [feature] });
|
||||
@@ -420,8 +413,6 @@ describe('meta tracing', () => {
|
||||
});
|
||||
},
|
||||
}),
|
||||
getSnapshot: () => ({ count: 0 }),
|
||||
subscribe: () => {},
|
||||
});
|
||||
|
||||
const store = createStore({
|
||||
@@ -450,8 +441,6 @@ describe('meta tracing', () => {
|
||||
});
|
||||
},
|
||||
}),
|
||||
getSnapshot: () => ({ loading: false }),
|
||||
subscribe: () => {},
|
||||
});
|
||||
|
||||
const store = createStore({ features: [feature] });
|
||||
@@ -472,45 +461,47 @@ describe('meta tracing', () => {
|
||||
});
|
||||
|
||||
describe('sync actions', () => {
|
||||
it('target() allows sync mutations without task', () => {
|
||||
it('task handler allows sync mutations', async () => {
|
||||
class Target {
|
||||
volume = 1;
|
||||
}
|
||||
|
||||
const feature = defineFeature<Target>()({
|
||||
state: ({ target }) => ({
|
||||
state: ({ task }) => ({
|
||||
volume: 1,
|
||||
setVolume(value: number) {
|
||||
target().volume = value;
|
||||
return task(({ target }) => {
|
||||
target.volume = value;
|
||||
});
|
||||
},
|
||||
}),
|
||||
getSnapshot: ({ target: t }) => ({ volume: t.volume }),
|
||||
subscribe: () => {},
|
||||
|
||||
attach({ target: t, set }) {
|
||||
set({ volume: t.volume });
|
||||
},
|
||||
});
|
||||
|
||||
const store = createStore({ features: [feature] });
|
||||
const targetInstance = new Target();
|
||||
|
||||
store.attach(targetInstance);
|
||||
store.setVolume(0.5);
|
||||
await store.setVolume(0.5);
|
||||
|
||||
expect(targetInstance.volume).toBe(0.5);
|
||||
});
|
||||
|
||||
it('target() throws when not attached', () => {
|
||||
it('task throws when not attached', async () => {
|
||||
const feature = defineFeature<unknown>()({
|
||||
state: ({ target }) => ({
|
||||
state: ({ task }) => ({
|
||||
value: 0,
|
||||
doSomething() {
|
||||
target();
|
||||
return task(() => {});
|
||||
},
|
||||
}),
|
||||
getSnapshot: () => ({ value: 0 }),
|
||||
subscribe: () => {},
|
||||
});
|
||||
|
||||
const store = createStore({ features: [feature] });
|
||||
|
||||
expect(() => store.doSomething()).toThrow('NO_TARGET');
|
||||
await expect(store.doSomething()).rejects.toThrow('NO_TARGET');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,12 +23,6 @@ describe('createState', () => {
|
||||
expect(state.current.muted).toBe(false);
|
||||
});
|
||||
|
||||
it('reflects changes after set', () => {
|
||||
const state = createTestState();
|
||||
state.set('volume', 0.5);
|
||||
expect(state.current.volume).toBe(0.5);
|
||||
});
|
||||
|
||||
it('reflects changes after patch', () => {
|
||||
const state = createTestState();
|
||||
state.patch({ volume: 0.5, muted: true });
|
||||
@@ -37,24 +31,6 @@ describe('createState', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('set', () => {
|
||||
it('updates a single key', () => {
|
||||
const state = createTestState();
|
||||
state.set('volume', 0.5);
|
||||
expect(state.current.volume).toBe(0.5);
|
||||
});
|
||||
|
||||
it('does not notify if value is the same', () => {
|
||||
const state = createTestState();
|
||||
const listener = vi.fn();
|
||||
state.subscribe(listener);
|
||||
|
||||
state.set('volume', 1); // same as initial
|
||||
flush();
|
||||
expect(listener).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('patch', () => {
|
||||
it('updates multiple keys', () => {
|
||||
const state = createTestState();
|
||||
@@ -81,7 +57,7 @@ describe('createState', () => {
|
||||
const listener = vi.fn();
|
||||
state.subscribe(listener);
|
||||
|
||||
state.set('volume', 0.5);
|
||||
state.patch({ volume: 0.5 });
|
||||
|
||||
expect(listener).not.toHaveBeenCalled();
|
||||
await Promise.resolve();
|
||||
@@ -93,21 +69,21 @@ describe('createState', () => {
|
||||
const listener = vi.fn();
|
||||
state.subscribe(listener);
|
||||
|
||||
state.set('volume', 0.5);
|
||||
state.patch({ volume: 0.5 });
|
||||
expect(listener).not.toHaveBeenCalled();
|
||||
|
||||
flush();
|
||||
expect(listener).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('batches multiple mutations into one notification', () => {
|
||||
it('batches multiple patches into one notification', () => {
|
||||
const state = createTestState();
|
||||
const listener = vi.fn();
|
||||
state.subscribe(listener);
|
||||
|
||||
state.set('volume', 0.5);
|
||||
state.set('muted', true);
|
||||
state.set('currentTime', 10);
|
||||
state.patch({ volume: 0.5 });
|
||||
state.patch({ muted: true });
|
||||
state.patch({ currentTime: 10 });
|
||||
|
||||
flush();
|
||||
expect(listener).toHaveBeenCalledOnce();
|
||||
@@ -118,12 +94,12 @@ describe('createState', () => {
|
||||
const listener = vi.fn();
|
||||
|
||||
const unsub = state.subscribe(listener);
|
||||
state.set('volume', 0.5);
|
||||
state.patch({ volume: 0.5 });
|
||||
flush();
|
||||
expect(listener).toHaveBeenCalledOnce();
|
||||
|
||||
unsub();
|
||||
state.set('volume', 0.3);
|
||||
state.patch({ volume: 0.3 });
|
||||
flush();
|
||||
expect(listener).toHaveBeenCalledOnce(); // still 1
|
||||
});
|
||||
|
||||
@@ -31,14 +31,15 @@ describe('store', () => {
|
||||
});
|
||||
},
|
||||
}),
|
||||
getSnapshot: ({ target }) => ({
|
||||
volume: target.volume,
|
||||
muted: target.muted,
|
||||
}),
|
||||
subscribe: ({ target, update, signal }) => {
|
||||
target.addEventListener('volumechange', update);
|
||||
|
||||
attach({ target, signal, set }) {
|
||||
const sync = () => set({ volume: target.volume, muted: target.muted });
|
||||
|
||||
sync();
|
||||
|
||||
target.addEventListener('volumechange', sync);
|
||||
signal.addEventListener('abort', () => {
|
||||
target.removeEventListener('volumechange', update);
|
||||
target.removeEventListener('volumechange', sync);
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -65,8 +66,10 @@ describe('store', () => {
|
||||
});
|
||||
},
|
||||
}),
|
||||
getSnapshot: ({ target }) => ({ paused: target.paused }),
|
||||
subscribe: () => {},
|
||||
|
||||
attach({ target, set }) {
|
||||
set({ paused: target.paused });
|
||||
},
|
||||
});
|
||||
|
||||
describe('creation', () => {
|
||||
@@ -234,8 +237,6 @@ describe('store', () => {
|
||||
});
|
||||
},
|
||||
}),
|
||||
getSnapshot: () => ({ value: 0 }),
|
||||
subscribe: () => {},
|
||||
});
|
||||
|
||||
const store = createStore({
|
||||
@@ -272,8 +273,6 @@ describe('store', () => {
|
||||
});
|
||||
},
|
||||
}),
|
||||
getSnapshot: () => ({ value: 0 }),
|
||||
subscribe: () => {},
|
||||
});
|
||||
|
||||
const store = createStore({
|
||||
@@ -306,8 +305,6 @@ describe('store', () => {
|
||||
});
|
||||
},
|
||||
}),
|
||||
getSnapshot: () => ({ value: 0 }),
|
||||
subscribe: () => {},
|
||||
});
|
||||
|
||||
const store = createStore({
|
||||
@@ -404,8 +401,6 @@ describe('store', () => {
|
||||
});
|
||||
},
|
||||
}),
|
||||
getSnapshot: () => ({ value: 0 }),
|
||||
subscribe: () => {},
|
||||
});
|
||||
|
||||
const store = createStore({
|
||||
|
||||
Reference in New Issue
Block a user