fix: add more tests, fix some minor things
This commit is contained in:
parent
6d52fde043
commit
34f1ed4fb5
|
@ -31,9 +31,9 @@
|
|||
"dev:server": "node ./test/adhoc/server.js",
|
||||
"lint": "standard && stylelint '**/*.scss'",
|
||||
"lint:fix": "standard --fix && stylelint --fix '**/*.scss'",
|
||||
"test": "jest --clearCache && jest",
|
||||
"test": "jest --no-cache",
|
||||
"test:adhoc": "node ./test/adhoc/server.js",
|
||||
"cover": "jest --clearCache && jest --coverage"
|
||||
"cover": "jest --no-cache --coverage"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
TIMEOUT_BEFORE_LOADING_MESSAGE
|
||||
} from '../../constants'
|
||||
import { uniqBy } from '../../../shared/uniqBy'
|
||||
import { mergeI18n } from '../../utils/mergeI18n'
|
||||
|
||||
let skinToneEmoji = DEFAULT_SKIN_TONE_EMOJI
|
||||
let i18n = enI18n
|
||||
|
@ -65,9 +66,6 @@ emojiSupportLevelPromise.then(level => {
|
|||
$: {
|
||||
// show a Loading message if it takes a long time, or show an error if there's a network/IDB error
|
||||
async function handleDatabaseLoading () {
|
||||
if (!database) {
|
||||
return
|
||||
}
|
||||
const timeoutHandle = setTimeout(() => {
|
||||
message = i18n.loading
|
||||
}, TIMEOUT_BEFORE_LOADING_MESSAGE)
|
||||
|
@ -83,7 +81,9 @@ $: {
|
|||
}
|
||||
}
|
||||
}
|
||||
/* no await */ handleDatabaseLoading()
|
||||
if (database) {
|
||||
/* no await */ handleDatabaseLoading()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this is a bizarre way to set these default properties, but currently Svelte
|
||||
|
@ -92,10 +92,16 @@ $: {
|
|||
// the dataSource, which is bad. Delaying with a microtask avoids this.
|
||||
Promise.resolve().then(() => {
|
||||
if (!database) {
|
||||
database = database || new Database({ dataSource: DEFAULT_DATA_SOURCE, locale: DEFAULT_LOCALE })
|
||||
database = new Database({ dataSource: DEFAULT_DATA_SOURCE, locale: DEFAULT_LOCALE })
|
||||
}
|
||||
})
|
||||
|
||||
$: {
|
||||
if (i18n !== enI18n) {
|
||||
i18n = mergeI18n(enI18n, i18n) // if partial translations are provided, merge with English
|
||||
}
|
||||
}
|
||||
|
||||
$: style = `
|
||||
--num-categories: ${categories.length};
|
||||
--indicator-opacity: ${searchMode ? 0 : 1};
|
||||
|
@ -112,10 +118,14 @@ $: skinTones = Array(NUM_SKIN_TONES).fill().map((_, i) => skinToneTextForSkinTon
|
|||
// (where ResizeObserver is supported).
|
||||
const resizeObserverSupported = typeof ResizeObserver === 'function'
|
||||
$: currentCategoryIndex = categories.findIndex(_ => _.group === currentCategory.group)
|
||||
$: indicatorStyle = (resizeObserverSupported
|
||||
? `transform: translateX(${currentCategoryIndex * computedIndicatorWidth}px);` // exact pixels
|
||||
: `transform: translateX(${currentCategoryIndex * 100}%);`// fallback to percent-based
|
||||
)
|
||||
$: {
|
||||
/* istanbul ignore if */
|
||||
if (resizeObserverSupported) {
|
||||
indicatorStyle = `transform: translateX(${currentCategoryIndex * computedIndicatorWidth}px);` // exact pixels
|
||||
} else {
|
||||
indicatorStyle = `transform: translateX(${currentCategoryIndex * 100}%);`// fallback to percent-based
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
async function updatePreferredSkinTone () {
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
function mergeArray (base, target) {
|
||||
const res = []
|
||||
for (let i = 0; i < base.length; i++) {
|
||||
res[i] = target[i] || base[i]
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
function mergeObject (base, target) {
|
||||
const res = {}
|
||||
for (const [key, baseValue] of Object.entries(base)) {
|
||||
const targetValue = target[key]
|
||||
let mergedValue
|
||||
if (Array.isArray(baseValue)) {
|
||||
mergedValue = mergeArray(baseValue, targetValue)
|
||||
} else if (typeof baseValue === 'object') {
|
||||
mergedValue = mergeObject(baseValue, targetValue)
|
||||
} else { // primitive
|
||||
mergedValue = targetValue || baseValue
|
||||
}
|
||||
res[key] = mergedValue
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
export function mergeI18n (base, target) {
|
||||
return mergeObject(base, target)
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
import Database from '../../../src/database/Database'
|
||||
import frEmoji from 'emojibase-data/fr/data.json'
|
||||
import {
|
||||
basicAfterEach, basicBeforeEach, ALL_EMOJI, ALL_EMOJI_MISCONFIGURED_ETAG,
|
||||
ALL_EMOJI_NO_ETAG, truncateEmoji, tick
|
||||
ALL_EMOJI_NO_ETAG, tick, mockFrenchDataSource, FR_EMOJI
|
||||
} from '../shared'
|
||||
|
||||
describe('database tests', () => {
|
||||
|
@ -152,14 +151,10 @@ describe('database tests', () => {
|
|||
})
|
||||
|
||||
test('multiple databases in multiple locales', async () => {
|
||||
const truncatedFrEmoji = truncateEmoji(frEmoji)
|
||||
const dataSourceFr = 'http://localhost/fr.json'
|
||||
|
||||
fetch.get(dataSourceFr, () => new Response(JSON.stringify(truncatedFrEmoji), { headers: { ETag: 'W/zzz' } }))
|
||||
fetch.head(dataSourceFr, () => new Response(null, { headers: { ETag: 'W/zzz' } }))
|
||||
mockFrenchDataSource()
|
||||
|
||||
const en = new Database({ dataSource: ALL_EMOJI })
|
||||
const fr = new Database({ dataSource: dataSourceFr, locale: 'fr' })
|
||||
const fr = new Database({ dataSource: FR_EMOJI, locale: 'fr' })
|
||||
|
||||
expect((await en.getEmojiBySearchQuery('monkey face')).map(_ => _.annotation)).toStrictEqual(['monkey face'])
|
||||
expect((await fr.getEmojiBySearchQuery('tête singe')).map(_ => _.annotation)).toStrictEqual(['tête de singe'])
|
||||
|
|
|
@ -2,7 +2,6 @@ import { basicBeforeEach, basicAfterEach, ALL_EMOJI, truncatedEmoji, tick } from
|
|||
import * as testingLibrary from '@testing-library/dom'
|
||||
import Picker from '../../../src/picker/PickerElement.js'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { DEFAULT_SKIN_TONE_EMOJI } from '../../../src/picker/constants'
|
||||
|
||||
const { waitFor, fireEvent } = testingLibrary
|
||||
const { type } = userEvent
|
||||
|
@ -46,12 +45,16 @@ describe('Picker tests', () => {
|
|||
expect(getAllByRole('tab')).toHaveLength(9)
|
||||
|
||||
expect(getByRole('tab', { name: 'Smileys and emoticons', selected: true })).toBeVisible()
|
||||
expect(testingLibrary.getAllByRole(getByRole('tabpanel'), 'menuitem')).toHaveLength(numInGroup1)
|
||||
await waitFor(() => expect(
|
||||
testingLibrary.getAllByRole(getByRole('tabpanel'), 'menuitem')).toHaveLength(numInGroup1)
|
||||
)
|
||||
|
||||
expect(getByRole('tab', { name: 'People and body' })).toBeVisible()
|
||||
fireEvent.click(getByRole('tab', { name: 'People and body' }))
|
||||
|
||||
await waitFor(() => expect(testingLibrary.getAllByRole(getByRole('tabpanel'), 'menuitem')).toHaveLength(numInGroup2))
|
||||
await waitFor(() => expect(
|
||||
testingLibrary.getAllByRole(getByRole('tabpanel'), 'menuitem')).toHaveLength(numInGroup2))
|
||||
|
||||
expect(getByRole('tab', { name: 'People and body', selected: true })).toBeVisible()
|
||||
})
|
||||
|
||||
|
@ -87,6 +90,8 @@ describe('Picker tests', () => {
|
|||
await pressKeyAndExpectActiveOption('ArrowUp', 'Light', 1)
|
||||
await pressKeyAndExpectActiveOption('ArrowUp', 'Default', 0)
|
||||
await pressKeyAndExpectActiveOption('ArrowUp', 'Dark', 5)
|
||||
await pressKeyAndExpectActiveOption('ArrowDown', 'Default', 5)
|
||||
await pressKeyAndExpectActiveOption('ArrowUp', 'Dark', 5)
|
||||
|
||||
await fireEvent.click(activeElement(), { key: 'Enter', code: 'Enter' })
|
||||
|
||||
|
@ -248,10 +253,12 @@ describe('Picker tests', () => {
|
|||
}))
|
||||
})
|
||||
|
||||
test('Change default skin tone emoji', async () => {
|
||||
expect(getByRole('button', { name: /Choose a skin tone/ }).innerHTML).toContain(DEFAULT_SKIN_TONE_EMOJI)
|
||||
picker.skinToneEmoji = '👇'
|
||||
await waitFor(() => expect(getByRole('button', { name: /Choose a skin tone/ }).innerHTML)
|
||||
.toContain('👇'))
|
||||
test('Closes skintone picker when blurred', async () => {
|
||||
fireEvent.click(getByRole('button', { name: /Choose a skin tone/ }))
|
||||
await waitFor(() => expect(getByRole('listbox', { name: 'Skin tones' })).toBeVisible())
|
||||
// Simulating a focusout event is hard, have to both focus and blur
|
||||
getByRole('searchbox').focus()
|
||||
fireEvent.focusOut(getByRole('listbox', { name: 'Skin tones' }))
|
||||
await waitFor(() => expect(queryAllByRole('listbox', { name: 'Skin tones' })).toHaveLength(0))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
import {
|
||||
ALL_EMOJI,
|
||||
basicAfterEach,
|
||||
basicBeforeEach,
|
||||
FR_EMOJI,
|
||||
mockFrenchDataSource,
|
||||
tick
|
||||
} from '../shared'
|
||||
import Picker from '../../../src/picker/PickerElement'
|
||||
import { getAllByRole, getByRole, waitFor } from '@testing-library/dom'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { mergeI18n } from '../../../src/picker/utils/mergeI18n'
|
||||
import enI18n from '../../../src/picker/i18n/en'
|
||||
import Database from '../../../src/database/Database'
|
||||
import { DEFAULT_SKIN_TONE_EMOJI } from '../../../src/picker/constants'
|
||||
const { type, clear } = userEvent
|
||||
|
||||
describe('element tests', () => {
|
||||
describe('data tests', () => {
|
||||
test('can merge i18n object, partial translation', async () => {
|
||||
const partialFrI18n = {
|
||||
search: 'Recherche',
|
||||
skinTones: [
|
||||
'Défaut'
|
||||
],
|
||||
categories: {
|
||||
'smileys-emotion': 'Sourires et emoticons'
|
||||
}
|
||||
}
|
||||
const expected = JSON.parse(JSON.stringify(enI18n))
|
||||
expected.search = partialFrI18n.search
|
||||
expected.skinTones[0] = partialFrI18n.skinTones[0]
|
||||
expected.categories['smileys-emotion'] = partialFrI18n.categories['smileys-emotion']
|
||||
const mergedI18n = mergeI18n(enI18n, partialFrI18n)
|
||||
expect(mergedI18n).toStrictEqual(expected)
|
||||
})
|
||||
})
|
||||
describe('UI tests', () => {
|
||||
let picker
|
||||
let container
|
||||
|
||||
beforeEach(async () => {
|
||||
basicBeforeEach()
|
||||
mockFrenchDataSource()
|
||||
picker = new Picker({ dataSource: ALL_EMOJI, locale: 'en' })
|
||||
container = picker.shadowRoot.querySelector('.picker')
|
||||
document.body.appendChild(picker)
|
||||
await tick(20)
|
||||
})
|
||||
afterEach(async () => {
|
||||
await new Database({ dataSource: FR_EMOJI, locale: 'fr' }).delete()
|
||||
await new Database({ dataSource: ALL_EMOJI, locale: 'en' }).delete()
|
||||
await tick(20)
|
||||
basicAfterEach()
|
||||
document.body.removeChild(picker)
|
||||
})
|
||||
|
||||
test('changing locale/dataSource causes only one network request', async () => {
|
||||
expect(fetch).toHaveBeenCalledTimes(1)
|
||||
expect(fetch).toHaveBeenLastCalledWith(ALL_EMOJI, undefined)
|
||||
await type(getByRole(container, 'searchbox'), 'monkey face')
|
||||
await waitFor(() => expect(getAllByRole(container, 'option')).toHaveLength(1))
|
||||
expect(getByRole(container, 'option', { name: /🐵/ })).toBeVisible()
|
||||
picker.locale = 'fr'
|
||||
picker.dataSource = FR_EMOJI
|
||||
await tick(20)
|
||||
expect(fetch).toHaveBeenCalledTimes(2)
|
||||
expect(fetch).toHaveBeenLastCalledWith(FR_EMOJI, undefined)
|
||||
await clear(getByRole(container, 'searchbox'))
|
||||
await type(getByRole(container, 'searchbox'), 'singe tête')
|
||||
await waitFor(() => expect(getAllByRole(container, 'option')).toHaveLength(1))
|
||||
expect(getByRole(container, 'option', { name: /🐵/ })).toBeVisible()
|
||||
})
|
||||
|
||||
test('can dynamically change i18n', async () => {
|
||||
const partialFrI18n = {
|
||||
search: 'Recherche',
|
||||
skinTones: [
|
||||
'Défaut'
|
||||
],
|
||||
categories: {
|
||||
'smileys-emotion': 'Sourires et emoticons'
|
||||
}
|
||||
}
|
||||
await tick(20)
|
||||
picker.i18n = partialFrI18n
|
||||
expect(getByRole(container, 'searchbox', { name: 'Recherche' })).toBeVisible()
|
||||
expect(getByRole(container, 'tab', { name: 'Sourires et emoticons' })).toBeVisible()
|
||||
expect(getByRole(container, 'button', { name: 'Choose a skin tone (currently Défaut)' })).toBeVisible()
|
||||
})
|
||||
|
||||
test('can change default skin tone emoji', async () => {
|
||||
expect(getByRole(container, 'button', { name: /Choose a skin tone/ }).innerHTML).toContain(DEFAULT_SKIN_TONE_EMOJI)
|
||||
picker.skinToneEmoji = '👇'
|
||||
expect(getByRole(container, 'button', { name: /Choose a skin tone/ }).innerHTML).toContain('👇')
|
||||
picker.skinToneEmoji = '👋'
|
||||
expect(getByRole(container, 'button', { name: /Choose a skin tone/ }).innerHTML).toContain('👋')
|
||||
})
|
||||
|
||||
test('can get the locale/dataSource', () => {
|
||||
expect(picker.locale).toBe('en')
|
||||
expect(picker.dataSource).toBe(ALL_EMOJI)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -24,6 +24,8 @@ describe('Favorites UI', () => {
|
|||
document.body.appendChild(picker)
|
||||
const container = picker.shadowRoot.querySelector('.picker')
|
||||
|
||||
await tick(20)
|
||||
|
||||
// using a testId because testing-library seems to think role=menu has no aria-label
|
||||
const favoritesBar = getByTestId(container, 'favorites')
|
||||
expect(favoritesBar).toBeVisible()
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import allEmoji from 'emojibase-data/en/data.json'
|
||||
import frEmoji from 'emojibase-data/fr/data.json'
|
||||
|
||||
export function truncateEmoji (allEmoji) {
|
||||
// just take the first few emoji from each category, or else it takes forever to insert
|
||||
|
@ -18,10 +19,12 @@ export function truncateEmoji (allEmoji) {
|
|||
}
|
||||
|
||||
export const truncatedEmoji = truncateEmoji(allEmoji)
|
||||
const truncatedFrEmoji = truncateEmoji(frEmoji)
|
||||
|
||||
export const ALL_EMOJI = 'http://localhost/emoji.json'
|
||||
export const ALL_EMOJI_NO_ETAG = 'http://localhost/emoji-no-etag.json'
|
||||
export const ALL_EMOJI_MISCONFIGURED_ETAG = 'http://localhost/emoji-misconfigured-etag.json'
|
||||
export const FR_EMOJI = 'http://localhost/fr.json'
|
||||
|
||||
export function basicBeforeEach () {
|
||||
fetch
|
||||
|
@ -49,3 +52,8 @@ export async function tick (times = 1) {
|
|||
await new Promise(resolve => setTimeout(resolve))
|
||||
}
|
||||
}
|
||||
|
||||
export function mockFrenchDataSource () {
|
||||
fetch.get(FR_EMOJI, () => new Response(JSON.stringify(truncatedFrEmoji), { headers: { ETag: 'W/zzz' } }))
|
||||
fetch.head(FR_EMOJI, () => new Response(null, { headers: { ETag: 'W/zzz' } }))
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue