Warning, /firebird/firebird-ng/src/app/utils/config-property.spec.ts is written in an unsupported language. File is not indexed.
0001 import { ConfigProperty } from './config-property';
0002
0003 // Mock storage for testing
0004 class MockStorage {
0005 private storage: Map<string, string> = new Map();
0006
0007 getItem(key: string): string | null {
0008 return this.storage.get(key) || null;
0009 }
0010
0011 setItem(key: string, value: string): void {
0012 this.storage.set(key, value);
0013 }
0014
0015 clear(): void {
0016 this.storage.clear();
0017 }
0018
0019 // Helper to inspect storage contents for testing
0020 getAll(): Map<string, string> {
0021 return new Map(this.storage);
0022 }
0023 }
0024
0025 describe('ConfigProperty', () => {
0026 let mockStorage: MockStorage;
0027
0028 beforeEach(() => {
0029 mockStorage = new MockStorage();
0030 });
0031
0032 describe('constructor', () => {
0033 it('should initialize with default value when storage is empty', () => {
0034 const prop = new ConfigProperty('testKey', 'defaultValue', undefined, undefined, mockStorage);
0035 expect(prop.value).toBe('defaultValue');
0036 });
0037
0038 it('should load value from storage if present', () => {
0039 // For string values, storage holds the raw string (not JSON-encoded)
0040 mockStorage.setItem('testKey', 'storedValue');
0041 const prop = new ConfigProperty('testKey', 'defaultValue', undefined, undefined, mockStorage);
0042 expect(prop.value).toBe('storedValue');
0043 });
0044
0045 it('should use default if stored value fails validation', () => {
0046 // For string values, storage holds the raw string (not JSON-encoded)
0047 mockStorage.setItem('testKey', 'invalid');
0048 const validator = (v: string) => v.startsWith('valid');
0049 const prop = new ConfigProperty('testKey', 'validDefault', undefined, validator, mockStorage);
0050 expect(prop.value).toBe('validDefault');
0051 });
0052 });
0053
0054 describe('setValue', () => {
0055 it('should update value and storage', () => {
0056 const prop = new ConfigProperty('testKey', 'default', undefined, undefined, mockStorage);
0057 prop.setValue('newValue');
0058 expect(prop.value).toBe('newValue');
0059 expect(mockStorage.getItem('testKey')).toBe('newValue');
0060 });
0061
0062 it('should update timestamp when setting value', () => {
0063 const prop = new ConfigProperty('testKey', 'default', undefined, undefined, mockStorage);
0064 const beforeTime = Date.now();
0065 prop.setValue('newValue');
0066 const afterTime = Date.now();
0067
0068 const timestamp = prop.getTimestamp();
0069 expect(timestamp).not.toBeNull();
0070 expect(timestamp!).toBeGreaterThanOrEqual(beforeTime);
0071 expect(timestamp!).toBeLessThanOrEqual(afterTime);
0072 });
0073
0074 it('should use explicit timestamp when provided', () => {
0075 const prop = new ConfigProperty('testKey', 'default', undefined, undefined, mockStorage);
0076 const explicitTime = 1000000;
0077 prop.setValue('newValue', explicitTime);
0078 expect(prop.getTimestamp()).toBe(explicitTime);
0079 });
0080
0081 it('should not update if explicit timestamp is older than stored', () => {
0082 const prop = new ConfigProperty('testKey', 'default', undefined, undefined, mockStorage);
0083 prop.setValue('first', 2000);
0084 prop.setValue('second', 1000); // older timestamp
0085 expect(prop.value).toBe('first');
0086 });
0087
0088 it('should reject invalid values', () => {
0089 const validator = (v: number) => v > 0;
0090 const prop = new ConfigProperty<number>('testKey', 10, undefined, validator, mockStorage);
0091 prop.setValue(-5 as number);
0092 expect(prop.value).toBe(10); // unchanged
0093 });
0094
0095 it('should call saveCallback after setting value', () => {
0096 const callback = vi.fn();
0097 const prop = new ConfigProperty('testKey', 'default', callback, undefined, mockStorage);
0098 prop.setValue('newValue');
0099 expect(callback).toHaveBeenCalled();
0100 });
0101 });
0102
0103 describe('setDefault', () => {
0104 it('should reset value to default', () => {
0105 const prop = new ConfigProperty('testKey', 'default', undefined, undefined, mockStorage);
0106 prop.setValue('changed');
0107 expect(prop.value).toBe('changed');
0108
0109 prop.setDefault();
0110 expect(prop.value).toBe('default');
0111 });
0112
0113 it('should persist default value to storage', () => {
0114 const prop = new ConfigProperty('testKey', 'default', undefined, undefined, mockStorage);
0115 prop.setValue('changed');
0116 expect(mockStorage.getItem('testKey')).toBe('changed');
0117
0118 prop.setDefault();
0119 expect(mockStorage.getItem('testKey')).toBe('default');
0120 });
0121
0122 it('should update timestamp when setting default', () => {
0123 const prop = new ConfigProperty('testKey', 'default', undefined, undefined, mockStorage);
0124
0125 // Set a value with an old timestamp
0126 const oldTime = 1000;
0127 prop.setValue('changed', oldTime);
0128 expect(prop.getTimestamp()).toBe(oldTime);
0129
0130 // setDefault should update the timestamp to current time
0131 const beforeReset = Date.now();
0132 prop.setDefault();
0133 const afterReset = Date.now();
0134
0135 const newTimestamp = prop.getTimestamp();
0136 expect(newTimestamp).not.toBeNull();
0137 expect(newTimestamp!).toBeGreaterThanOrEqual(beforeReset);
0138 expect(newTimestamp!).toBeLessThanOrEqual(afterReset);
0139 });
0140
0141 it('should allow subsequent setValue after setDefault respects timestamp logic', () => {
0142 const prop = new ConfigProperty('testKey', 'default', undefined, undefined, mockStorage);
0143
0144 // Set value with old timestamp
0145 prop.setValue('old', 1000);
0146
0147 // Reset to default (gets current timestamp)
0148 prop.setDefault();
0149 const resetTimestamp = prop.getTimestamp();
0150
0151 // Try to set with older timestamp - should be rejected
0152 prop.setValue('older', 500);
0153 expect(prop.value).toBe('default');
0154
0155 // Set with newer timestamp - should succeed
0156 prop.setValue('newer', resetTimestamp! + 1000);
0157 expect(prop.value).toBe('newer');
0158 });
0159
0160 it('should call saveCallback when setting default', () => {
0161 const callback = vi.fn();
0162 const prop = new ConfigProperty('testKey', 'default', callback, undefined, mockStorage);
0163
0164 callback.mockClear();
0165 prop.setDefault();
0166 expect(callback).toHaveBeenCalled();
0167 });
0168
0169 it('should notify subscribers when setting default', () => {
0170 const prop = new ConfigProperty('testKey', 'default', undefined, undefined, mockStorage);
0171 prop.setValue('changed');
0172
0173 const values: string[] = [];
0174 prop.changes$.subscribe(v => values.push(v));
0175
0176 prop.setDefault();
0177
0178 expect(values).toContain('default');
0179 });
0180 });
0181
0182 describe('value getter/setter', () => {
0183 it('should get current value', () => {
0184 const prop = new ConfigProperty('testKey', 'default', undefined, undefined, mockStorage);
0185 expect(prop.value).toBe('default');
0186 });
0187
0188 it('should set value using property setter', () => {
0189 const prop = new ConfigProperty('testKey', 'default', undefined, undefined, mockStorage);
0190 prop.value = 'newValue';
0191 expect(prop.value).toBe('newValue');
0192 });
0193 });
0194
0195 describe('key getter', () => {
0196 it('should return the key', () => {
0197 const prop = new ConfigProperty('myKey', 'default', undefined, undefined, mockStorage);
0198 expect(prop.key).toBe('myKey');
0199 });
0200 });
0201
0202 describe('getTimestamp', () => {
0203 it('should return null when no timestamp is stored', () => {
0204 const prop = new ConfigProperty('testKey', 'default', undefined, undefined, mockStorage);
0205 // Before any setValue, there's no timestamp
0206 expect(prop.getTimestamp()).toBeNull();
0207 });
0208
0209 it('should return timestamp after setValue', () => {
0210 const prop = new ConfigProperty('testKey', 'default', undefined, undefined, mockStorage);
0211 prop.setValue('value');
0212 expect(prop.getTimestamp()).not.toBeNull();
0213 });
0214 });
0215
0216 describe('changes$ observable', () => {
0217 it('should emit initial value', () => {
0218 const prop = new ConfigProperty('testKey', 'default', undefined, undefined, mockStorage);
0219 let emittedValue: string | undefined;
0220 prop.changes$.subscribe(v => emittedValue = v);
0221 expect(emittedValue).toBe('default');
0222 });
0223
0224 it('should emit on value changes', () => {
0225 const prop = new ConfigProperty('testKey', 'default', undefined, undefined, mockStorage);
0226 const values: string[] = [];
0227 prop.changes$.subscribe(v => values.push(v));
0228
0229 prop.setValue('first');
0230 prop.setValue('second');
0231
0232 expect(values).toEqual(['default', 'first', 'second']);
0233 });
0234 });
0235
0236 describe('type handling', () => {
0237 it('should handle string values', () => {
0238 const prop = new ConfigProperty('testKey', 'default', undefined, undefined, mockStorage);
0239 prop.setValue('hello');
0240 expect(mockStorage.getItem('testKey')).toBe('hello');
0241 });
0242
0243 it('should handle number values', () => {
0244 const prop = new ConfigProperty('testKey', 42, undefined, undefined, mockStorage);
0245 prop.setValue(100);
0246 expect(mockStorage.getItem('testKey')).toBe('100');
0247 expect(prop.value).toBe(100);
0248 });
0249
0250 it('should handle boolean values', () => {
0251 const prop = new ConfigProperty('testKey', true, undefined, undefined, mockStorage);
0252 prop.setValue(false);
0253 expect(mockStorage.getItem('testKey')).toBe('false');
0254 expect(prop.value).toBe(false);
0255 });
0256
0257 it('should handle object values', () => {
0258 const prop = new ConfigProperty<Record<string, number>>('testKey', { a: 1 }, undefined, undefined, mockStorage);
0259 prop.setValue({ b: 2 });
0260 expect(mockStorage.getItem('testKey')).toBe('{"b":2}');
0261 expect(prop.value).toEqual({ b: 2 });
0262 });
0263
0264 it('should handle array values', () => {
0265 const prop = new ConfigProperty('testKey', [1, 2], undefined, undefined, mockStorage);
0266 prop.setValue([3, 4, 5]);
0267 expect(mockStorage.getItem('testKey')).toBe('[3,4,5]');
0268 expect(prop.value).toEqual([3, 4, 5]);
0269 });
0270 });
0271 });