diff --git a/src/models/localizedModel.js b/src/models/localizedModel.js index c4c7d77..b28376a 100644 --- a/src/models/localizedModel.js +++ b/src/models/localizedModel.js @@ -7,7 +7,10 @@ class LocalizedModel{ const model = this; this._localizedProxy = new Proxy(this, { set(target, prop, newVal){ - const attrMap = model._strMap[prop]; + var attrMap = model._strMap[prop]; + if(!attrMap && prop !== '_strMap' && prop !== '_localeKey' && prop !== '_localizedProxy'){ + attrMap = model._strMap[prop] = {}; + } if(attrMap){ model._setLocalizedValue(prop, newVal); } @@ -97,7 +100,10 @@ class LocalizedModel{ return new Proxy(object, { set(target, prop, newVal){ const resolvedAttrKey = `${attrKey}.${prop}`; - const attrMap = model._strMap[resolvedAttrKey]; + var attrMap = model._strMap[resolvedAttrKey]; + if(!attrMap && prop !== '_strMap' && prop !== '_localeKey' && prop !== '_localizedProxy'){ + attrMap = model._strMap[resolvedAttrKey] = {}; + } if(attrMap){ model._setLocalizedValue(resolvedAttrKey, newVal); } @@ -153,6 +159,7 @@ class LocalizedModel{ */ inflateLocales(attrKeyMap, arrIndex){ var localeObj = {}; + var localeKeysMap = {}; Object.keys(attrKeyMap).forEach((cacheKey) => { var resolvedCacheKey = cacheKey; @@ -162,6 +169,7 @@ class LocalizedModel{ let attrMap = this._strMap[resolvedCacheKey]; if(attrMap){ Object.keys(attrMap).forEach(function(localeKey){ + localeKeysMap[localeKey] = true; const outputKey = attrKeyMap[cacheKey]; if(!Object.prototype.hasOwnProperty.call(localeObj, localeKey)){ localeObj[localeKey] = {}; @@ -171,6 +179,17 @@ class LocalizedModel{ } }) + Object.keys(localeKeysMap).forEach(function(localeKey){ + let localeMap = localeObj[localeKey] + + Object.keys(attrKeyMap).forEach((cacheKey) => { + const outputKey = attrKeyMap[cacheKey]; + if(!Object.prototype.hasOwnProperty.call(localeMap, outputKey)){ + localeMap[outputKey] = ''; + } + }) + }) + return localeObj; } } diff --git a/src/utils/num.js b/src/utils/num.js new file mode 100644 index 0000000..8c86ac5 --- /dev/null +++ b/src/utils/num.js @@ -0,0 +1,10 @@ +var NumUtil = { + isNumber: function(val){ + return typeof val === 'number' && !isNaN(val); + }, + isStrNumber: function(val){ + return NumUtil.isNumber(+val); + } +}; + +export default NumUtil; \ No newline at end of file diff --git a/src/utils/obj.js b/src/utils/obj.js index fc25ac7..9bcdc54 100644 --- a/src/utils/obj.js +++ b/src/utils/obj.js @@ -1,4 +1,9 @@ +import NumUtil from "./num"; + var ObjUtil = { + isObject: function(obj){ + return typeof obj === 'object' && obj !== null; + }, getByStr: function (obj, keyStr) { // convert indexes to properties keyStr = keyStr.replace(/\[(\w+)\]/g, '.$1'); @@ -7,7 +12,7 @@ var ObjUtil = { var aKeys = keyStr.split('.'); for (var i = 0, n = aKeys.length; i < n; ++i) { var key = aKeys[i]; - if (key in obj) { + if (ObjUtil.isObject(obj) && key in obj) { obj = obj[key]; } else { return; @@ -26,7 +31,7 @@ var ObjUtil = { if (key in obj && i + 1 < n) { obj = obj[key]; } else if (!(key in obj) && i + 1 < n) { - obj[key] = {}; + obj[key] = NumUtil.isStrNumber(aKeys[i+1]) ? [] : {}; obj = obj[key]; } else { obj[key] = val; diff --git a/tests/object.spec.js b/tests/object.spec.js index 4cc389c..834b35c 100644 --- a/tests/object.spec.js +++ b/tests/object.spec.js @@ -80,4 +80,30 @@ test('Update respective proxied locale values', () => { let serverPayload = appObj.toJSON(); expect(serverPayload?.secondLevel?.localizations?.en?.header).toBe('englishtest'); expect(serverPayload?.secondLevel?.localizations?.fr?.header).toBe('frenchtest'); +}) + +test('New instance setting attribute will serialize', () => { + let appObj = new AppObject(); + appObj.secondLevelObj.header = 'first value'; + appObj.localize('fr'); + appObj.secondLevelObj.header = 'french value'; + let serverPayload = appObj.toJSON(); + expect(serverPayload?.secondLevel).toBeInstanceOf(Object) + expect(serverPayload?.secondLevel?.localizations?.en?.header).toBe('first value') + expect(serverPayload?.secondLevel?.localizations?.fr?.header).toBe('french value') +}) + +test('Serialization is symmetrical across locales even for empty values', () => { + let appObj = new AppObject(); + appObj.secondLevelObj.header = 'first value'; + appObj.localize('fr'); + appObj.secondLevelObj.header = 'french value'; + let serverPayload = appObj.toJSON(); + expect(serverPayload?.secondLevel).toBeInstanceOf(Object) + expect(serverPayload?.secondLevel?.localizations?.en?.header).toBe('first value') + expect(serverPayload?.secondLevel?.localizations?.en?.subHeader).toBe(''); + expect(serverPayload?.secondLevel?.localizations?.en?.description).toBe(''); + expect(serverPayload?.secondLevel?.localizations?.fr?.header).toBe('french value') + expect(serverPayload?.secondLevel?.localizations?.fr?.subHeader).toBe(''); + expect(serverPayload?.secondLevel?.localizations?.fr?.description).toBe(''); }) \ No newline at end of file diff --git a/tests/toplevel.spec.js b/tests/toplevel.spec.js index e854bfa..1cb9c89 100644 --- a/tests/toplevel.spec.js +++ b/tests/toplevel.spec.js @@ -78,4 +78,28 @@ test('Update respective proxied locale values', () => { let serverPayload = appObj.toJSON(); expect(serverPayload?.localizations?.en?.header).toBe('englishtest'); expect(serverPayload?.localizations?.fr?.header).toBe('frenchtest'); +}) + +test('New instance setting attribute will serialize', () => { + let appObj = new AppObject(); + appObj.header = 'first value'; + appObj.localize('fr'); + appObj.header = 'french value'; + let serverPayload = appObj.toJSON(); + expect(serverPayload?.localizations?.en?.header).toBe('first value') + expect(serverPayload?.localizations?.fr?.header).toBe('french value') +}) + +test('Serialization is symmetrical across locales even for empty values', () => { + let appObj = new AppObject(); + appObj.header = 'first value'; + appObj.localize('fr'); + appObj.header = 'french value'; + let serverPayload = appObj.toJSON(); + expect(serverPayload?.localizations?.en?.header).toBe('first value'); + expect(serverPayload?.localizations?.en?.subHeader).toBe(''); + expect(serverPayload?.localizations?.en?.description).toBe(''); + expect(serverPayload?.localizations?.fr?.header).toBe('french value'); + expect(serverPayload?.localizations?.fr?.subHeader).toBe(''); + expect(serverPayload?.localizations?.fr?.description).toBe(''); }) \ No newline at end of file