diff --git a/packages/core/tsdown.config.ts b/packages/core/tsdown.config.ts index e5fd07a3..dd23d7a3 100644 --- a/packages/core/tsdown.config.ts +++ b/packages/core/tsdown.config.ts @@ -23,9 +23,6 @@ const createConfig = (mode: BuildMode): UserConfig => ({ clean: true, hash: false, unbundle: true, - alias: { - '@': new URL('./src/core', import.meta.url).pathname, - }, outDir: `dist/${mode}`, define: { __DEV__: mode === 'dev' ? 'true' : 'false', diff --git a/packages/store/src/core/combine.ts b/packages/store/src/core/combine.ts index 32e19efd..2f0d622d 100644 --- a/packages/store/src/core/combine.ts +++ b/packages/store/src/core/combine.ts @@ -12,6 +12,19 @@ export function combine[]>( return { state: (ctx: StateContext) => { const states = slices.map((slice) => slice.state(ctx)); + + if (__DEV__) { + const seen = new Set(); + for (const state of states) { + for (const key of Object.keys(state as object)) { + if (seen.has(key)) { + console.warn(`[vjs-store] combine(): duplicate state key "${key}" — later slice overwrites earlier one`); + } + seen.add(key); + } + } + } + return Object.assign({}, ...states) as UnionSliceState; }, diff --git a/packages/store/src/core/globals.d.ts b/packages/store/src/core/globals.d.ts new file mode 100644 index 00000000..b867229b --- /dev/null +++ b/packages/store/src/core/globals.d.ts @@ -0,0 +1 @@ +declare const __DEV__: boolean; diff --git a/packages/store/src/core/tests/combine.test.ts b/packages/store/src/core/tests/combine.test.ts new file mode 100644 index 00000000..82cefcac --- /dev/null +++ b/packages/store/src/core/tests/combine.test.ts @@ -0,0 +1,67 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { combine } from '../combine'; +import { defineSlice } from '../slice'; +import { createStore } from '../store'; + +class MockTarget extends EventTarget { + value = 0; +} + +const slice = defineSlice(); + +describe('combine', () => { + it('merges state from multiple slices', () => { + const a = slice({ state: () => ({ count: 0 }) }); + const b = slice({ state: () => ({ label: 'hello' }) }); + + const store = createStore()(combine(a, b)); + + expect(store.state).toMatchObject({ count: 0, label: 'hello' }); + }); + + it('calls attach for each slice', () => { + const attachA = vi.fn(); + const attachB = vi.fn(); + + const a = slice({ state: () => ({ count: 0 }), attach: attachA }); + const b = slice({ state: () => ({ label: '' }), attach: attachB }); + + const store = createStore()(combine(a, b)); + store.attach(new MockTarget()); + + expect(attachA).toHaveBeenCalledOnce(); + expect(attachB).toHaveBeenCalledOnce(); + }); + + it('catches and reports attach errors via onError callback', () => { + const error = new Error('attach failed'); + const onError = vi.fn(); + + const a = slice({ + state: () => ({ count: 0 }), + attach: () => { + throw error; + }, + }); + const b = slice({ state: () => ({ label: '' }) }); + + const store = createStore()(combine(a, b), { onError }); + store.attach(new MockTarget()); + + expect(onError).toHaveBeenCalled(); + }); + + it('warns on duplicate state keys in __DEV__ mode', () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + const a = slice({ state: () => ({ count: 0 }) }); + const b = slice({ state: () => ({ count: 1 }) }); + + createStore()(combine(a, b)); + + expect(warn).toHaveBeenCalledWith(expect.stringContaining('duplicate state key "count"')); + + warn.mockRestore(); + }); +}); diff --git a/packages/store/tsdown.config.ts b/packages/store/tsdown.config.ts index 542162f2..f693dd8f 100644 --- a/packages/store/tsdown.config.ts +++ b/packages/store/tsdown.config.ts @@ -17,9 +17,6 @@ const createConfig = (mode: BuildMode): UserConfig => ({ clean: true, hash: false, unbundle: true, - alias: { - '@': new URL('./src/core', import.meta.url).pathname, - }, outDir: `dist/${mode}`, define: { __DEV__: mode === 'dev' ? 'true' : 'false', diff --git a/packages/utils/tsdown.config.ts b/packages/utils/tsdown.config.ts index bd2679f5..e441c6e5 100644 --- a/packages/utils/tsdown.config.ts +++ b/packages/utils/tsdown.config.ts @@ -20,8 +20,5 @@ export default defineConfig({ clean: true, hash: false, unbundle: true, - alias: { - '@': new URL('./src', import.meta.url).pathname, - }, dts: true, });