feat: add customCategorySort function (#43)

Fixes #41
This commit is contained in:
Nolan Lawson 2020-09-13 11:39:14 -07:00 committed by GitHub
parent 561ba3afd4
commit 6be51f1806
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 109 additions and 6 deletions

View File

@ -36,6 +36,7 @@ Features:
* [JavaScript API](#javascript-api)
+ [Picker](#picker)
- [i18n structure](#i18n-structure)
- [Custom category order](#custom-category-order)
+ [Database](#database)
- [Constructors](#constructors)
* [constructor](#constructor)
@ -232,7 +233,8 @@ The `new Picker(options)` constructor supports several options:
Name | Type | Default | Description |
------ | ------ | ------ | ------ |
`customEmoji` | CustomEmoji[] | - | Array of custom emoji |
`customCategorySort` | function | - | Function to sort custom category strings (sorted alphabetically by default) |
`customEmoji` | CustomEmoji[] | - | Array of custom emoji |
`dataSource` | string | "https://cdn.jsdelivr.net/npm/emojibase-data@5/en/data.json" | URL to fetch the emojibase data from (`data-source` when used as an attribute) |
`i18n` | I18n | - | i18n object (see below for details) |
`locale` | string | "en" | Locale string |
@ -319,6 +321,19 @@ Here is the default English `i18n` object (`"en"` locale):
Note that some of these strings are only visible to users of screen readers.
But you should still support them if you internationalize your app!
#### Custom category order
By default, custom categories are sorted alphabetically. To change this, pass in your own `customCategorySort`:
```js
picker.customCategorySort = (category1, category2) => { /* your sorting code */ };
```
This function should accept two strings and return a number.
Custom emoji with no category will pass in `undefined`. By default, these are shown first, with the label `"Custom"`
(determined by `i18n.categories.custom`).
### Database
You can work with the database API separately, which allows you to query emoji the same

View File

@ -35,6 +35,7 @@ let skinToneEmoji = DEFAULT_SKIN_TONE_EMOJI
let i18n = enI18n
let database = null
let customEmoji = null
let customCategorySort = (a, b) => a < b ? -1 : a > b ? 1 : 0
// private
let initialLoad = true
@ -406,7 +407,7 @@ $: {
}
return [...categoriesToEmoji.entries()]
.map(([category, emojis]) => ({ category, emojis }))
.sort((a, b) => a.category < b.category ? -1 : 1)
.sort((a, b) => customCategorySort(a.category, b.category))
}
currentEmojisWithCategories = calculateCurrentEmojisWithCategories()
@ -614,5 +615,6 @@ export {
database,
i18n,
skinToneEmoji,
customEmoji
customEmoji,
customCategorySort
}

View File

@ -6,6 +6,7 @@ export default class Picker extends HTMLElement {
i18n: I18n
skinToneEmoji: string
customEmoji?: CustomEmoji[]
customCategorySort?: (a: string, b: string) => number
/**
*
@ -14,13 +15,15 @@ export default class Picker extends HTMLElement {
* @param i18n - i18n object (see below for details)
* @param skinToneEmoji - The emoji to use for the skin tone picker (`skin-tone-emoji` when used as an attribute)
* @param customEmoji - Array of custom emoji
* @param customCategorySort - Function to sort custom category strings (sorted alphabetically by default)
*/
constructor({
dataSource = 'https://cdn.jsdelivr.net/npm/emojibase-data@5/en/data.json',
locale = 'en',
i18n,
skinToneEmoji = '🖐️',
customEmoji
customEmoji,
customCategorySort
}: PickerConstructorOptions = {}) {
super()
}

View File

@ -38,6 +38,7 @@ export interface PickerConstructorOptions {
i18n?: I18n
skinToneEmoji?: string
customEmoji?: CustomEmoji[]
customCategorySort?: (a: string, b: string) => number
}
export interface I18n {

View File

@ -4,6 +4,7 @@ import Picker from '../../../src/picker/PickerElement.js'
import userEvent from '@testing-library/user-event'
import { groups } from '../../../src/picker/groups'
import Database from '../../../src/database/Database'
import { getAccessibleName } from '../utils'
const { waitFor, fireEvent } = testingLibrary
const { type } = userEvent
@ -358,11 +359,80 @@ describe('Picker tests', () => {
await waitFor(() => expect(getByRole('menuitem', { name: 'donkey' })).toBeVisible())
await waitFor(() => expect(getByRole('menuitem', { name: 'monkey' })).toBeVisible())
await waitFor(() => expect(getByRole('menuitem', { name: 'horse' })).toBeVisible())
// TODO: can't actually test the category names because they're only exposed as menus, and
// testing-library doesn't seem to understand that menus can have aria-labels
// confirm alphabetical order for categories
expect(
await Promise.all(getAllByRole('menu').map(node => getAccessibleName(container, node)))
).toStrictEqual(
['Custom', 'Primates', 'Ungulates', 'Favorites']
)
// try searching
await type(getByRole('combobox'), 'donkey')
await waitFor(() => expect(getByRole('option', { name: 'donkey' })).toBeVisible())
})
test('Custom emoji with sorted categories', async () => {
picker.customEmoji = [
{
name: 'monkey',
shortcodes: ['monkey'],
url: 'monkey.png',
category: 'Primates'
},
{
name: 'donkey',
shortcodes: ['donkey'],
url: 'donkey.png',
category: 'Ungulates'
},
{
name: 'horse',
shortcodes: ['horse'],
url: 'horse.png',
category: 'Ungulates'
},
{
name: 'bird',
shortcodes: ['bird'],
url: 'bird.png',
category: 'Avians'
},
{
name: 'human',
shortcodes: ['human'],
url: 'human.png'
}
]
await waitFor(() => expect(getAllByRole('tab')).toHaveLength(groups.length + 1))
await waitFor(() => expect(getAllByRole('menu')).toHaveLength(5)) // favorites + four custom categories
// confirm alphabetical order for categories
expect(
await Promise.all(getAllByRole('menu').map(node => getAccessibleName(container, node)))
).toStrictEqual([
'Custom',
'Avians',
'Primates',
'Ungulates',
'Favorites'
])
const order = ['Ungulates', 'Primates', 'Avians']
picker.customCategorySort = (a, b) => {
const aIdx = order.indexOf(a)
const bIdx = order.indexOf(b)
return aIdx < bIdx ? -1 : 1
}
await waitFor(async () => (
expect(
await Promise.all(getAllByRole('menu').map(node => getAccessibleName(container, node)))
).toStrictEqual([
'Custom',
...order,
'Favorites'
])
))
})
})

12
test/spec/utils.js Normal file
View File

@ -0,0 +1,12 @@
import * as testingLibrary from '@testing-library/dom'
export async function getAccessibleName (container, node) {
let label = node.getAttribute('aria-label')
if (!label) {
const labeledBy = node.getAttribute('aria-labelledby')
if (labeledBy) {
label = testingLibrary.getNodeText(await container.getRootNode().getElementById(labeledBy))
}
}
return label
}