Warning, /firebird/firebird-ng/src/app/utils/persistent-property.spec.ts is written in an unsupported language. File is not indexed.
0001 import { PersistentProperty } from './persistent-property';
0002
0003 describe('ConfigProperty', () => {
0004 let mockStorage: { [key: string]: string };
0005 let mockStorageInterface: any;
0006
0007 beforeEach(() => {
0008 // Create a mock storage that simulates localStorage
0009 mockStorage = {};
0010 mockStorageInterface = {
0011 getItem: (key: string) => mockStorage[key] || null,
0012 setItem: (key: string, value: string) => { mockStorage[key] = value; }
0013 };
0014 });
0015
0016 afterEach(() => {
0017 mockStorage = {};
0018 });
0019
0020 describe('Basic functionality', () => {
0021 it('should create with default value', () => {
0022 const config = new PersistentProperty('test', 'defaultValue', undefined, undefined, mockStorageInterface);
0023 expect(config.value).toBe('defaultValue');
0024 });
0025
0026 it('should save and load string values', () => {
0027 const config = new PersistentProperty('test', 'default', undefined, undefined, mockStorageInterface);
0028 config.value = 'newValue';
0029
0030 // Create a new instance to verify persistence
0031 const config2 = new PersistentProperty('test', 'default', undefined, undefined, mockStorageInterface);
0032 expect(config2.value).toBe('newValue');
0033 });
0034
0035 it('should save and load object values', () => {
0036 const defaultObj = { name: 'default', count: 0 };
0037 const config = new PersistentProperty('test', defaultObj, undefined, undefined, mockStorageInterface);
0038
0039 const newObj = { name: 'updated', count: 42 };
0040 config.value = newObj;
0041
0042 // Create a new instance to verify persistence
0043 const config2 = new PersistentProperty('test', defaultObj, undefined, undefined, mockStorageInterface);
0044 expect(config2.value).toEqual(newObj);
0045 });
0046
0047 it('should validate values when validator is provided', () => {
0048 const validator = (value: number) => value > 0 && value < 100;
0049 const config = new PersistentProperty<number>('test', 50, undefined, validator, mockStorageInterface);
0050
0051 // Valid value should be accepted
0052 config.value = 75;
0053 expect(config.value).toBe(75);
0054
0055 // Invalid value should be rejected
0056 const consoleSpy = spyOn(console, 'error');
0057 config.value = 150;
0058 expect(config.value).toBe(75); // Should still be the previous valid value
0059 expect(consoleSpy).toHaveBeenCalledWith('Validation failed for:', 150);
0060 });
0061
0062 it('should call saveCallback when value is set', () => {
0063 const saveCallback = jasmine.createSpy('saveCallback');
0064 const config = new PersistentProperty('test', 'default', saveCallback, undefined, mockStorageInterface);
0065
0066 config.value = 'newValue';
0067 expect(saveCallback).toHaveBeenCalled();
0068 });
0069
0070 it('should emit changes through Observable', (done) => {
0071 const config = new PersistentProperty('test', 'initial', undefined, undefined, mockStorageInterface);
0072
0073 let emissionCount = 0;
0074 const expectedValues = ['initial', 'second', 'third'];
0075
0076 config.changes$.subscribe(value => {
0077 expect(value).toBe(expectedValues[emissionCount]);
0078 emissionCount++;
0079
0080 if (emissionCount === 3) {
0081 done();
0082 }
0083 });
0084
0085 config.value = 'second';
0086 config.value = 'third';
0087 });
0088 });
0089
0090 describe('Time-based configuration', () => {
0091 it('should store timestamp when saving value without explicit time', () => {
0092 const config = new PersistentProperty('test', 'default', undefined, undefined, mockStorageInterface);
0093 const beforeTime = Date.now();
0094
0095 config.value = 'newValue';
0096
0097 const afterTime = Date.now();
0098 const storedTime = parseInt(mockStorage['test.time'], 10);
0099
0100 expect(storedTime).toBeGreaterThanOrEqual(beforeTime);
0101 expect(storedTime).toBeLessThanOrEqual(afterTime);
0102 });
0103
0104 it('should store timestamp with setValue when time is provided', () => {
0105 const config = new PersistentProperty('test', 'default', undefined, undefined, mockStorageInterface);
0106 const specificTime = 1234567890;
0107
0108 config.setValue('newValue', specificTime);
0109
0110 expect(mockStorage['test.time']).toBe('1234567890');
0111 expect(config.value).toBe('newValue');
0112 });
0113
0114 it('should only update if provided timestamp is newer than stored', () => {
0115 const config = new PersistentProperty('test', 'default', undefined, undefined, mockStorageInterface);
0116
0117 // Set initial value with timestamp
0118 config.setValue('value1', 1000);
0119 expect(config.value).toBe('value1');
0120
0121 // Try to set with older timestamp - should be rejected
0122 const consoleSpy = spyOn(console, 'log');
0123 config.setValue('value2', 500);
0124 expect(config.value).toBe('value1'); // Should remain unchanged
0125 expect(consoleSpy).toHaveBeenCalled();
0126
0127 // Set with newer timestamp - should be accepted
0128 config.setValue('value3', 1500);
0129 expect(config.value).toBe('value3');
0130 });
0131
0132 it('should accept update when no timestamp exists in storage', () => {
0133 const config = new PersistentProperty('test', 'default', undefined, undefined, mockStorageInterface);
0134
0135 // Manually set a value without timestamp (simulating old data)
0136 mockStorage['test'] = '"oldValue"';
0137
0138 // Create new instance and update with timestamp
0139 const config2 = new PersistentProperty('test', 'default', undefined, undefined, mockStorageInterface);
0140 config2.setValue('newValue', 1000);
0141
0142 expect(config2.value).toBe('newValue');
0143 expect(mockStorage['test.time']).toBe('1000');
0144 });
0145
0146 it('should handle concurrent updates correctly', () => {
0147 // Simulate two different sources trying to update the same config
0148 const config1 = new PersistentProperty('shared', 'initial', undefined, undefined, mockStorageInterface);
0149 const config2 = new PersistentProperty('shared', 'initial', undefined, undefined, mockStorageInterface);
0150
0151 // Source 1 updates at time 1000
0152 config1.setValue('update1', 1000);
0153 expect(config1.value).toBe('update1');
0154
0155 // Source 2 tries to update with older timestamp - should fail
0156 const consoleSpy = spyOn(console, 'log');
0157 config2.setValue('update2', 900);
0158
0159 // Reload config2 to get the latest value
0160 const config2Reloaded = new PersistentProperty('shared', 'initial', undefined, undefined, mockStorageInterface);
0161 expect(config2Reloaded.value).toBe('update1'); // Should still be update1
0162
0163 // Source 2 updates with newer timestamp - should succeed
0164 config2.setValue('update3', 1100);
0165 const config1Reloaded = new PersistentProperty('shared', 'initial', undefined, undefined, mockStorageInterface);
0166 expect(config1Reloaded.value).toBe('update3');
0167 });
0168
0169 it('should use current time when no timestamp is provided to setValue', () => {
0170 const config = new PersistentProperty('test', 'default', undefined, undefined, mockStorageInterface);
0171 const beforeTime = Date.now();
0172
0173 config.setValue('newValue'); // No timestamp provided
0174
0175 const afterTime = Date.now();
0176 const storedTime = parseInt(mockStorage['test.time'], 10);
0177
0178 expect(storedTime).toBeGreaterThanOrEqual(beforeTime);
0179 expect(storedTime).toBeLessThanOrEqual(afterTime);
0180 });
0181
0182 it('should not call saveCallback when update is rejected due to older timestamp', () => {
0183 const saveCallback = jasmine.createSpy('saveCallback');
0184 const config = new PersistentProperty('test', 'default', saveCallback, undefined, mockStorageInterface);
0185
0186 // Set initial value with timestamp
0187 config.setValue('value1', 1000);
0188 expect(saveCallback).toHaveBeenCalledTimes(1);
0189
0190 // Try to set with older timestamp
0191 saveCallback.calls.reset();
0192 config.setValue('value2', 500);
0193 expect(saveCallback).not.toHaveBeenCalled();
0194 });
0195
0196 it('should not emit changes when update is rejected due to older timestamp', (done) => {
0197 const config = new PersistentProperty('test', 'initial', undefined, undefined, mockStorageInterface);
0198
0199 // Set initial value with timestamp
0200 config.setValue('value1', 1000);
0201
0202 let emissionCount = 0;
0203 config.changes$.subscribe(value => {
0204 emissionCount++;
0205 if (emissionCount === 1) {
0206 expect(value).toBe('value1'); // First emission from subscription
0207 } else if (emissionCount === 2) {
0208 expect(value).toBe('value3'); // Should skip value2
0209 done();
0210 } else {
0211 fail('Unexpected emission');
0212 }
0213 });
0214
0215 // This should be rejected
0216 setTimeout(() => {
0217 config.setValue('value2', 500);
0218
0219 // This should be accepted
0220 setTimeout(() => {
0221 config.setValue('value3', 1500);
0222 }, 10);
0223 }, 10);
0224 });
0225
0226 it('should handle array values with timestamps correctly', () => {
0227 const config = new PersistentProperty<number[]>('test', [], undefined, undefined, mockStorageInterface);
0228
0229 config.setValue([1, 2, 3], 1000);
0230 expect(config.value).toEqual([1, 2, 3]);
0231 expect(mockStorage['test.time']).toBe('1000');
0232
0233 // Try to update with older timestamp
0234 config.setValue([4, 5, 6], 900);
0235 expect(config.value).toEqual([1, 2, 3]); // Should remain unchanged
0236
0237 // Update with newer timestamp
0238 config.setValue([7, 8, 9], 1100);
0239 expect(config.value).toEqual([7, 8, 9]);
0240 expect(mockStorage['test.time']).toBe('1100');
0241 });
0242 });
0243
0244 describe('Error handling', () => {
0245 it('should handle corrupted stored values gracefully', () => {
0246 // Store invalid JSON
0247 mockStorage['test'] = 'invalid json {]';
0248
0249 const consoleSpy = spyOn(console, 'error');
0250 const config = new PersistentProperty('test', { default: true }, undefined, undefined, mockStorageInterface);
0251
0252 expect(config.value).toEqual({ default: true }); // Should fall back to default
0253 expect(consoleSpy).toHaveBeenCalled();
0254 });
0255
0256 it('should handle corrupted timestamp gracefully', () => {
0257 mockStorage['test'] = '"validValue"';
0258 mockStorage['test.time'] = 'not a number';
0259
0260 const consoleSpy = spyOn(console, 'error');
0261 const config = new PersistentProperty('test', 'default', undefined, undefined, mockStorageInterface);
0262
0263 // Should still allow updates when timestamp is corrupted
0264 config.setValue('newValue', 1000);
0265 expect(config.value).toBe('newValue');
0266 });
0267
0268 it('should handle missing storage gracefully', () => {
0269 const brokenStorage = {
0270 getItem: () => { throw new Error('Storage error'); },
0271 setItem: () => { throw new Error('Storage error'); }
0272 };
0273
0274 const consoleSpy = spyOn(console, 'error');
0275 const config = new PersistentProperty('test', 'default', undefined, undefined, brokenStorage);
0276
0277 expect(config.value).toBe('default'); // Should use default value
0278 expect(consoleSpy).toHaveBeenCalled();
0279 });
0280 });
0281
0282 describe('setDefault method', () => {
0283 it('should reset value to default', () => {
0284 const config = new PersistentProperty('test', 'defaultValue', undefined, undefined, mockStorageInterface);
0285
0286 config.value = 'newValue';
0287 expect(config.value).toBe('newValue');
0288
0289 config.setDefault();
0290 expect(config.value).toBe('defaultValue');
0291 });
0292 });
0293 });