test: 💍 add tests for dictionary store and utilities

This commit is contained in:
Christian Kaisermann 2019-11-25 21:41:05 -03:00
parent 07fb37e325
commit 965c18acc1
12 changed files with 201 additions and 44 deletions

View File

@ -1,5 +1,7 @@
import { register } from 'svelte-i18n'
import { register, setFallbackLocale } from 'svelte-i18n'
register('en', () => import('../messages/en.json'))
register('pt-BR', () => import('../messages/pt-BR.json'))
register('es-ES', () => import('../messages/es-ES.json'))
setFallbackLocale('en')

7
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "svelte-i18n",
"version": "2.0.1",
"version": "2.0.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -2502,6 +2502,11 @@
"integrity": "sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==",
"dev": true
},
"dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
},
"doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",

View File

@ -80,6 +80,7 @@
"dependencies": {
"commander": "^4.0.1",
"deepmerge": "^4.2.2",
"dlv": "^1.1.3",
"estree-walker": "^0.9.0",
"fast-memoize": "^2.5.1",
"intl-messageformat": "^7.5.2",

View File

@ -1,7 +1,4 @@
// todo invalidate only keys with null values
import resolvePath from 'object-resolve-path'
import { hasLocaleDictionary } from '../stores/dictionary'
import { getMessageFromDictionary } from '../stores/dictionary'
import { getFallbackOf } from '../stores/locale'
const lookupCache: Record<string, Record<string, string>> = {}
@ -13,26 +10,14 @@ const addToCache = (path: string, locale: string, message: string) => {
return message
}
export const lookupMessage = (
dictionary: any,
path: string,
locale: string
): string => {
export const lookupMessage = (path: string, locale: string): string => {
if (locale == null) return null
if (locale in lookupCache && path in lookupCache[locale]) {
return lookupCache[locale][path]
}
if (hasLocaleDictionary(locale)) {
if (path in dictionary[locale]) {
return addToCache(path, locale, dictionary[locale][path])
}
const message = resolvePath(dictionary[locale], path)
if (message) return addToCache(path, locale, message)
}
return addToCache(
path,
locale,
lookupMessage(dictionary, path, getFallbackOf(locale))
)
const message = getMessageFromDictionary(locale, path)
if (message) return message
return addToCache(path, locale, lookupMessage(path, getFallbackOf(locale)))
}

View File

@ -7,7 +7,11 @@ export function defineMessages(i: Record<string, MessageObject>) {
return i
}
export { $locale as locale, setInitialLocale } from './stores/locale'
export {
$locale as locale,
setInitialLocale,
setFallbackLocale,
} from './stores/locale'
export {
$dictionary as dictionary,
$locales as locales,
@ -26,4 +30,3 @@ export {
// @deprecated
export { getClientLocale } from './includes/utils'
export { setInitialLocale as waitInitialLocale } from './stores/locale'

View File

@ -1,12 +1,17 @@
import delve from 'dlv'
import merge from 'deepmerge'
import { writable, derived } from 'svelte/store'
import { LocaleDictionary } from '../types/index'
import { Dictionary } from '../types/index'
import { getFallbackOf } from './locale'
let dictionary: LocaleDictionary
const $dictionary = writable<LocaleDictionary>({})
let dictionary: Dictionary
const $dictionary = writable<Dictionary>({})
export function getLocaleDictionary(locale: string) {
return (dictionary[locale] as Dictionary) || null
}
export function getDictionary() {
return dictionary
@ -16,14 +21,28 @@ export function hasLocaleDictionary(locale: string) {
return locale in dictionary
}
export function getAvailableLocale(locale: string): string | null {
if (locale in dictionary || locale == null) return locale
return getAvailableLocale(getFallbackOf(locale))
export function getMessageFromDictionary(locale: string, id: string) {
if (hasLocaleDictionary(locale)) {
const localeDictionary = getLocaleDictionary(locale)
if (id in localeDictionary) {
return localeDictionary[id]
}
const message = delve(localeDictionary, id)
if (message) return message
}
return null
}
export function addMessages(locale: string, ...partials: LocaleDictionary[]) {
export function getClosestAvailableLocale(locale: string): string | null {
if (locale == null || hasLocaleDictionary(locale)) return locale
return getClosestAvailableLocale(getFallbackOf(locale))
}
export function addMessages(locale: string, ...partials: Dictionary[]) {
$dictionary.update(d => {
dictionary[locale] = merge.all([dictionary[locale] || {}].concat(partials))
dictionary[locale] = merge.all<Dictionary>(
[getLocaleDictionary(locale) || {}].concat(partials)
)
return d
})
}

View File

@ -11,7 +11,7 @@ import {
getNumberFormatter,
} from '../includes/formats'
import { getDictionary, $dictionary } from './dictionary'
import { $dictionary } from './dictionary'
import { getCurrentLocale, getFallbacksOf, $locale } from './locale'
const formatMessage: Formatter = (id, options = {}) => {
@ -28,7 +28,7 @@ const formatMessage: Formatter = (id, options = {}) => {
)
}
const message = lookupMessage(getDictionary(), id, locale)
const message = lookupMessage(id, locale)
if (!message) {
console.warn(

View File

@ -4,7 +4,7 @@ import { flushQueue, hasLocaleQueue } from '../includes/loaderQueue'
import { getClientLocale } from '../includes/utils'
import { GetClientLocaleOptions } from '../types'
import { getAvailableLocale } from './dictionary'
import { getClosestAvailableLocale } from './dictionary'
let fallbackLocale: string = null
let current: string
@ -71,7 +71,7 @@ $locale.subscribe((newLocale: string) => {
const localeSet = $locale.set
$locale.set = (newLocale: string): void | Promise<void> => {
if (getAvailableLocale(newLocale) && hasLocaleQueue(newLocale)) {
if (getClosestAvailableLocale(newLocale) && hasLocaleQueue(newLocale)) {
return flushQueue(newLocale).then(() => localeSet(newLocale))
}
return localeSet(newLocale)

View File

@ -1,5 +1,5 @@
export interface LocaleDictionary {
[key: string]: LocaleDictionary | LocaleDictionary[] | string | object
export interface Dictionary {
[key: string]: string | string[] | Dictionary | Dictionary[]
}
export interface MessageObject {

View File

@ -1,2 +1,2 @@
declare module 'object-resolve-path'
declare module 'dlv'
declare module 'nano-memoize'

View File

@ -0,0 +1,114 @@
import {
getDictionary,
hasLocaleDictionary,
getClosestAvailableLocale,
getMessageFromDictionary,
addMessages,
$dictionary,
$locales,
getLocaleDictionary,
} from '../../src/client/stores/dictionary'
import { get } from 'svelte/store'
beforeEach(() => {
$dictionary.set({})
})
test('adds a new dictionary to a locale', () => {
addMessages('en', { field_1: 'name' })
addMessages('pt', { field_1: 'nome' })
expect(get($dictionary)).toMatchObject({
en: { field_1: 'name' },
pt: { field_1: 'nome' },
})
})
test('merges the existing dictionaries with new ones', () => {
addMessages('en', { field_1: 'name', deep: { prop1: 'foo' } })
addMessages('en', { field_2: 'lastname', deep: { prop2: 'foo' } })
addMessages('pt', { field_1: 'nome', deep: { prop1: 'foo' } })
addMessages('pt', { field_2: 'sobrenome', deep: { prop2: 'foo' } })
expect(get($dictionary)).toMatchObject({
en: {
field_1: 'name',
field_2: 'lastname',
deep: { prop1: 'foo', prop2: 'foo' },
},
pt: {
field_1: 'nome',
field_2: 'sobrenome',
deep: { prop1: 'foo', prop2: 'foo' },
},
})
})
test('gets the whole current dictionary', () => {
addMessages('en', { field_1: 'name' })
expect(getDictionary()).toMatchObject({
en: { field_1: 'name' },
})
})
test('gets the dictionary of a locale', () => {
addMessages('en', { field_1: 'name' })
expect(getLocaleDictionary('en')).toMatchObject({ field_1: 'name' })
})
test('checks if a locale dictionary exists', () => {
addMessages('pt', { field_1: 'name' })
expect(hasLocaleDictionary('en')).toBe(false)
expect(hasLocaleDictionary('pt')).toBe(true)
})
test('gets the closest available locale', () => {
addMessages('pt', { field_1: 'name' })
expect(getClosestAvailableLocale('pt-BR')).toBe('pt')
})
test("returns null if there's no closest locale available", () => {
addMessages('pt', { field_1: 'name' })
expect(getClosestAvailableLocale('it-IT')).toBe(null)
})
test('lists all locales in the dictionary', () => {
addMessages('en', {})
addMessages('pt', {})
addMessages('pt-BR', {})
expect(get($locales)).toEqual(['en', 'pt', 'pt-BR'])
})
describe('getting messages', () => {
test('gets a message from a shallow dictionary', () => {
addMessages('en', { message: 'Some message' })
expect(getMessageFromDictionary('en', 'message')).toBe('Some message')
})
test('gets a message from a deep object in the dictionary', () => {
addMessages('en', { messages: { message_1: 'Some message' } })
expect(getMessageFromDictionary('en', 'messages.message_1')).toBe(
'Some message'
)
})
test('gets a message from an array in the dictionary', () => {
addMessages('en', { messages: ['Some message', 'Other message'] })
expect(getMessageFromDictionary('en', 'messages.0')).toBe('Some message')
expect(getMessageFromDictionary('en', 'messages.1')).toBe('Other message')
})
test('accepts english in dictionary keys', () => {
addMessages('pt', {
'Hey man, how are you today?': 'E ai cara, como você vai hoje?',
})
expect(getMessageFromDictionary('pt', 'Hey man, how are you today?')).toBe(
'E ai cara, como você vai hoje?'
)
})
test('returns null for missing messages', () => {
addMessages('en', {})
expect(getMessageFromDictionary('en', 'foo')).toBe(null)
})
})

View File

@ -9,9 +9,11 @@ import {
$locale,
isRelatedLocale,
} from '../../src/client/stores/locale'
import { get } from 'svelte/store'
beforeEach(() => {
setFallbackLocale(undefined)
$locale.set(undefined)
})
test('sets and gets the fallback locale', () => {
@ -33,7 +35,7 @@ test('checks if a locale is a fallback locale of another locale', () => {
expect(isRelatedLocale('en-US', 'it')).toBe(false)
})
test('gets the next fallback locale of a certain locale', () => {
test('gets the next fallback locale of a locale', () => {
expect(getFallbackOf('az-Cyrl-AZ')).toBe('az-Cyrl')
expect(getFallbackOf('en-US')).toBe('en')
expect(getFallbackOf('en')).toBe(null)
@ -54,13 +56,13 @@ test('if global fallback locale has a fallback, it should return it', () => {
expect(getFallbackOf('en-US')).toBe('en')
})
test('gets all fallback locales of a certain locale', () => {
test('gets all fallback locales of a locale', () => {
expect(getFallbacksOf('en-US')).toEqual(['en', 'en-US'])
expect(getFallbacksOf('en-US')).toEqual(['en', 'en-US'])
expect(getFallbacksOf('az-Cyrl-AZ')).toEqual(['az', 'az-Cyrl', 'az-Cyrl-AZ'])
})
test('gets all fallback locales of a certain locale including the global fallback locale', () => {
test('gets all fallback locales of a locale including the global fallback locale', () => {
setFallbackLocale('pt')
expect(getFallbacksOf('en-US')).toEqual(['en', 'en-US', 'pt'])
expect(getFallbacksOf('en-US')).toEqual(['en', 'en-US', 'pt'])
@ -71,9 +73,35 @@ test('gets all fallback locales of a certain locale including the global fallbac
'pt',
])
})
test('gets all fallback locales of a locale including the global fallback locale and its fallbacks', () => {
setFallbackLocale('pt-BR')
expect(getFallbacksOf('en-US')).toEqual(['en', 'en-US', 'pt', 'pt-BR'])
expect(getFallbacksOf('en-US')).toEqual(['en', 'en-US', 'pt', 'pt-BR'])
expect(getFallbacksOf('az-Cyrl-AZ')).toEqual([
'az',
'az-Cyrl',
'az-Cyrl-AZ',
'pt',
'pt-BR',
])
})
test('should not list fallback locale twice', () => {
test("don't list fallback locale twice", () => {
setFallbackLocale('pt-BR')
expect(getFallbacksOf('pt-BR')).toEqual(['pt', 'pt-BR'])
expect(getFallbacksOf('pt')).toEqual(['pt'])
})
test('gets the current locale', () => {
expect(getCurrentLocale()).toBe(undefined)
$locale.set('es-ES')
expect(getCurrentLocale()).toBe('es-ES')
})
test('sets the global fallback when defining initial locale', () => {
setInitialLocale({
fallback: 'pt',
})
expect(get($locale)).toBe('pt')
expect(getFallbackLocale()).toBe('pt')
})