fix(core): Fix state initialization, hash detection, and validation - v2.0.25
This commit is contained in:
157
test/test.initialization.both.ts
Normal file
157
test/test.initialization.both.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import { expect, tap } from '@push.rocks/tapbundle';
|
||||
import * as smartstate from '../ts/index.js';
|
||||
|
||||
type TTestStateParts = 'initTest' | 'persistTest' | 'forceTest';
|
||||
|
||||
interface ITestState {
|
||||
value: number;
|
||||
nested: {
|
||||
data: string;
|
||||
};
|
||||
}
|
||||
|
||||
tap.test('should handle soft init mode (default)', async () => {
|
||||
const state = new smartstate.Smartstate<TTestStateParts>();
|
||||
|
||||
// First creation
|
||||
const statePart1 = await state.getStatePart<ITestState>('initTest', {
|
||||
value: 1,
|
||||
nested: { data: 'initial' }
|
||||
});
|
||||
expect(statePart1.getState()).toEqual({
|
||||
value: 1,
|
||||
nested: { data: 'initial' }
|
||||
});
|
||||
|
||||
// Second call should return existing
|
||||
const statePart2 = await state.getStatePart<ITestState>('initTest');
|
||||
expect(statePart1).toEqual(statePart2);
|
||||
});
|
||||
|
||||
tap.test('should handle mandatory init mode', async () => {
|
||||
const state = new smartstate.Smartstate<TTestStateParts>();
|
||||
|
||||
// First creation should succeed
|
||||
const statePart1 = await state.getStatePart<ITestState>('initTest', {
|
||||
value: 1,
|
||||
nested: { data: 'initial' }
|
||||
}, 'mandatory');
|
||||
expect(statePart1).toBeInstanceOf(smartstate.StatePart);
|
||||
|
||||
// Second call with mandatory should fail
|
||||
let error: Error | null = null;
|
||||
try {
|
||||
await state.getStatePart<ITestState>('initTest', {
|
||||
value: 2,
|
||||
nested: { data: 'second' }
|
||||
}, 'mandatory');
|
||||
} catch (e) {
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).not.toBeNull();
|
||||
expect(error?.message).toMatch(/already exists.*mandatory/);
|
||||
});
|
||||
|
||||
tap.test('should handle force init mode', async () => {
|
||||
const state = new smartstate.Smartstate<TTestStateParts>();
|
||||
|
||||
// First creation
|
||||
const statePart1 = await state.getStatePart<ITestState>('forceTest', {
|
||||
value: 1,
|
||||
nested: { data: 'initial' }
|
||||
});
|
||||
expect(statePart1.getState()?.value).toEqual(1);
|
||||
|
||||
// Force should create new state part
|
||||
const statePart2 = await state.getStatePart<ITestState>('forceTest', {
|
||||
value: 2,
|
||||
nested: { data: 'forced' }
|
||||
}, 'force');
|
||||
expect(statePart2.getState()?.value).toEqual(2);
|
||||
expect(statePart1).not.toEqual(statePart2);
|
||||
});
|
||||
|
||||
tap.test('should handle missing initial state error', async () => {
|
||||
const state = new smartstate.Smartstate<TTestStateParts>();
|
||||
|
||||
let error: Error | null = null;
|
||||
try {
|
||||
await state.getStatePart<ITestState>('initTest');
|
||||
} catch (e) {
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).not.toBeNull();
|
||||
expect(error?.message).toMatch(/does not exist.*no initial state/);
|
||||
});
|
||||
|
||||
tap.test('should handle state validation', async () => {
|
||||
const state = new smartstate.Smartstate<TTestStateParts>();
|
||||
|
||||
const statePart = await state.getStatePart<ITestState>('initTest', {
|
||||
value: 1,
|
||||
nested: { data: 'initial' }
|
||||
});
|
||||
|
||||
// Setting null should fail validation
|
||||
let error: Error | null = null;
|
||||
try {
|
||||
await statePart.setState(null as any);
|
||||
} catch (e) {
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).not.toBeNull();
|
||||
expect(error?.message).toMatch(/Invalid state structure/);
|
||||
});
|
||||
|
||||
tap.test('should handle undefined state in select', async () => {
|
||||
const state = new smartstate.Smartstate<TTestStateParts>();
|
||||
const statePart = new smartstate.StatePart<TTestStateParts, ITestState>('initTest');
|
||||
|
||||
// Select should filter out undefined states
|
||||
const values: (ITestState | undefined)[] = [];
|
||||
statePart.select().subscribe(val => values.push(val));
|
||||
|
||||
// Initially undefined, should not emit
|
||||
expect(values).toHaveLength(0);
|
||||
|
||||
// After setting state, should emit
|
||||
await statePart.setState({
|
||||
value: 1,
|
||||
nested: { data: 'test' }
|
||||
});
|
||||
|
||||
expect(values).toHaveLength(1);
|
||||
expect(values[0]).toEqual({
|
||||
value: 1,
|
||||
nested: { data: 'test' }
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('should not notify on duplicate state', async () => {
|
||||
const state = new smartstate.Smartstate<TTestStateParts>();
|
||||
const statePart = await state.getStatePart<ITestState>('initTest', {
|
||||
value: 1,
|
||||
nested: { data: 'initial' }
|
||||
});
|
||||
|
||||
let notificationCount = 0;
|
||||
// Use select() to get initial value + changes
|
||||
statePart.select().subscribe(() => notificationCount++);
|
||||
|
||||
// Should have received initial state
|
||||
expect(notificationCount).toEqual(1);
|
||||
|
||||
// Set same state multiple times
|
||||
await statePart.setState({ value: 1, nested: { data: 'initial' } });
|
||||
await statePart.setState({ value: 1, nested: { data: 'initial' } });
|
||||
await statePart.setState({ value: 1, nested: { data: 'initial' } });
|
||||
|
||||
// Should still be 1 (no new notifications for duplicate state)
|
||||
expect(notificationCount).toEqual(1);
|
||||
|
||||
// Change state should notify
|
||||
await statePart.setState({ value: 2, nested: { data: 'changed' } });
|
||||
expect(notificationCount).toEqual(2);
|
||||
});
|
||||
|
||||
export default tap.start();
|
Reference in New Issue
Block a user