fix: add more tests, fix some minor things

This commit is contained in:
Nolan Lawson 2020-06-13 12:14:19 -07:00
parent 6d52fde043
commit 34f1ed4fb5
8 changed files with 182 additions and 27 deletions

View File

@ -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",

View File

@ -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 () {

View File

@ -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)
}

View File

@ -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'])

View File

@ -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))
})
})

View File

@ -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)
})
})
})

View File

@ -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()

View File

@ -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' } }))
}