From 4a0d444e02354d590f44f23de6f9b568dc22a29d Mon Sep 17 00:00:00 2001 From: alxndrsn Date: Fri, 3 May 2024 09:30:45 +0000 Subject: [PATCH] Handle toString; add tests for other builtins This changes current support for __proto__ in key paths. See: https://github.com/kazupon/vue-i18n/issues/1706 --- src/index.js | 3 +- src/path.js | 3 ++ test/unit/basic.test.js | 85 +++++++++++++++++++++++++++++++++++++- test/unit/fixture/index.js | 32 ++++++++++++-- test/unit/issues.test.js | 8 ++-- 5 files changed, 121 insertions(+), 10 deletions(-) diff --git a/src/index.js b/src/index.js index 5d47497f5..93ccbd7cc 100644 --- a/src/index.js +++ b/src/index.js @@ -165,8 +165,7 @@ export default class VueI18n { if (!message || !key) { return false } if (!isNull(this._path.getPathValue(message, key))) { return true } // fallback for flat key - if (message[key]) { return true } - return false + return Object.prototype.hasOwnProperty.call(message, key) } if (this._warnHtmlInMessage === 'warn' || this._warnHtmlInMessage === 'error') { diff --git a/src/path.js b/src/path.js index 6afeeb8b0..036790060 100644 --- a/src/path.js +++ b/src/path.js @@ -288,6 +288,9 @@ export default class I18nPath { let last: any = obj let i: number = 0 while (i < length) { + if (!Object.prototype.hasOwnProperty.call(last, paths[i])) { + return null + } const value: any = last[paths[i]] if (value === undefined || value === null) { return null diff --git a/test/unit/basic.test.js b/test/unit/basic.test.js index 9f8f67082..0a7fdfb67 100644 --- a/test/unit/basic.test.js +++ b/test/unit/basic.test.js @@ -636,7 +636,7 @@ describe('basic', () => { }) }) - describe('$te', () => { + describe.only('$te', () => { describe('existing key', () => { it('should return true', () => { const vm = new Vue({ i18n }) @@ -660,6 +660,89 @@ describe('basic', () => { assert(vm.$te('message.hello', 'xx') === false) }) }) + + describe('builtin property handling', () => { + [ + // '__proto__', // TODO: see https://github.com/kazupon/vue-i18n/issues/1706 + 'constructor', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'toLocaleString', + 'toString', + 'valueOf' + ].forEach(k => { + describe('top-level props', () => { + describe('existing key', () => { + let vm; + beforeEach(() => { + const i18n = new VueI18n({ + locale: 'en', + fallbackLocale: 'en', + messages: { + en: { + [k]: 'i exist' + }, + ja: { + [k]: 'i exist (ja)' + }, + }, + modifiers: { + custom: str => str.replace(/[aeiou]/g, 'x') + } + }) + vm = new Vue({ i18n }) + }) + + it('should return true', () => { + assert(vm.$te(k) === true) + }) + + it('should return true with locale', () => { + assert(vm.$te(k, 'ja') === true) + }) + }) + + describe('not existing key', () => { + it('should return false', () => { + const vm = new Vue({ i18n }) + assert(vm.$te(k) === false) + }) + + it('should return false with locale', () => { + const vm = new Vue({ i18n }) + assert(vm.$te(k, 'ja') === false) + }) + }) + }) + + describe('deep props', () => { + describe('existing key', () => { + it('should return true', () => { + const vm = new Vue({ i18n }) + assert(vm.$te(`issues.builtins.existing.${k}`) === true) + }) + + it('should return true with locale', () => { + const vm = new Vue({ i18n }) + assert(vm.$te(`issues.builtins.existing.${k}`, 'ja') === true) + }) + }) + + describe('not existing key', () => { + it('should return false', () => { + const vm = new Vue({ i18n }) + assert(vm.$te(`issues.builtins.missing.${k}`) === false) + }) + + it('should return false with locale', () => { + const vm = new Vue({ i18n }) + assert(vm.$te(`issues.builtins.missing.${k}`, 'ja') === false) + }) + }) + }) + }) + }) }) describe('i18n#locale', () => { diff --git a/test/unit/fixture/index.js b/test/unit/fixture/index.js index a81a96960..793062968 100644 --- a/test/unit/fixture/index.js +++ b/test/unit/fixture/index.js @@ -1,4 +1,4 @@ -export default { +module.exports = { en: { message: { hello: 'the world', @@ -70,7 +70,20 @@ export default { ] ], issues: { - arrayBugs: ['bug1', 'bug2'] + arrayBugs: ['bug1', 'bug2'], + builtins: { + existing: { + // '__proto__': 'i exist', // TODO + 'constructor': 'i exist', + 'hasOwnProperty': 'i exist', + 'isPrototypeOf': 'i exist', + 'propertyIsEnumerable': 'i exist', + 'toLocaleString': 'i exist', + 'toString': 'i exist', + 'valueOf': 'i exist' + }, + missing: {} + } }, 'foo.bar.buz': 'hello flat key!' }, @@ -111,7 +124,20 @@ export default { ] ], issues: { - arrayBugs: ['バグ1', 'バグ2'] + arrayBugs: ['バグ1', 'バグ2'], + builtins: { + existing: { + // '__proto__': 'i exist (ja)', // TODO + 'constructor': 'i exist (ja)', + 'hasOwnProperty': 'i exist (ja)', + 'isPrototypeOf': 'i exist (ja)', + 'propertyIsEnumerable': 'i exist (ja)', + 'toLocaleString': 'i exist (ja)', + 'toString': 'i exist (ja)', + 'valueOf': 'i exist (ja)' + }, + missing: {} + } }, 'foo.bar.buz': 'こんにちは、フラットなキーさん!' } diff --git a/test/unit/issues.test.js b/test/unit/issues.test.js index 5d30a973a..b568b2df1 100644 --- a/test/unit/issues.test.js +++ b/test/unit/issues.test.js @@ -321,7 +321,7 @@ describe('issues', () => { }) }) - describe('#349', () => { + describe.only('#349', () => { it('should be existed', done => { assert(vm.$te('foo.bar.buz') === true) done() @@ -372,7 +372,7 @@ describe('issues', () => { }) }) - describe('#398', () => { + describe.only('#398', () => { it('should return true', () => { assert.strictEqual(vm.$te('0123a'), true) assert.strictEqual(vm.$te('01234'), true) @@ -640,7 +640,7 @@ describe('issues', () => { }) }) - describe('#468', () => { + describe.only('#468', () => { it('should be existed', done => { assert(vm.$te('hello world') === true) done() @@ -690,7 +690,7 @@ describe('issues', () => { }) }) - describe('#515', () => { + describe.only('#515', () => { it('$te should return true for empty string', () => { assert.strictEqual(vm.$te('message.empty'), true) })