157 lines
4.6 KiB
TypeScript
157 lines
4.6 KiB
TypeScript
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(); |