mirror of
https://github.com/strapi/strapi.git
synced 2025-12-02 01:52:21 +00:00
chore: add docs and tests
chore: remove compo chore: add docs and tests for useFilter chore: add tests & docs for useCollator
This commit is contained in:
parent
7f1b43adf9
commit
ef4f3b64fe
49
docs/docs/core/helper-plugin/hooks/use-collator.mdx
Normal file
49
docs/docs/core/helper-plugin/hooks/use-collator.mdx
Normal file
@ -0,0 +1,49 @@
|
||||
---
|
||||
title: useCollator
|
||||
description: API reference for the useCollator hook in Strapi
|
||||
tags:
|
||||
- hooks
|
||||
- helper-plugin
|
||||
- i18n
|
||||
---
|
||||
|
||||
A custom hook that returns the [`Intl.Collator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator) native API.
|
||||
Useful for searching & sorting strings that are localised. The implementation method has a unique cache store to avoid creating a new instance of the
|
||||
`Intl.Collator` for each call of the hook depending on the locale and options provided.
|
||||
|
||||
Stolen from [`react-aria`](https://github.com/adobe/react-spectrum/blob/main/packages/%40react-aria/i18n/src/useCollator.ts).
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useCollator } from '@strapi/helper-plugin';
|
||||
|
||||
const MyComponent = () => {
|
||||
const { locale } = useIntl();
|
||||
const fruits = ['banana', 'apple', 'orange', 'kiwi'];
|
||||
|
||||
const formatter = useCollator(locale, { sensitivity: 'base' });
|
||||
|
||||
/**
|
||||
* This would render the list of fruits in the following order:
|
||||
* apple
|
||||
* banana
|
||||
* kiwi
|
||||
* orange
|
||||
*/
|
||||
return (
|
||||
<div>
|
||||
{fruits.sort(formatter.compare).map((fruit) => (
|
||||
<h2 key={fruit}>{fruit}</h2>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Typescript
|
||||
|
||||
```ts
|
||||
function useCollator(locale: string, options?: Intl.CollatorOptions): Intl.Collator;
|
||||
```
|
||||
59
docs/docs/core/helper-plugin/hooks/use-filter.mdx
Normal file
59
docs/docs/core/helper-plugin/hooks/use-filter.mdx
Normal file
@ -0,0 +1,59 @@
|
||||
---
|
||||
title: useFilter
|
||||
description: API reference for the useFilter hook in Strapi
|
||||
tags:
|
||||
- hooks
|
||||
- helper-plugin
|
||||
- i18n
|
||||
---
|
||||
|
||||
Provides localized string search functionality that is useful for filtering or matching items in a list. Options can be
|
||||
provided to adjust the sensitivity to case, diacritics, and other parameters.
|
||||
|
||||
Stolen from [`react-aria`](https://github.com/adobe/react-spectrum/blob/main/packages/%40react-aria/i18n/src/useFilter.ts).
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useFilter } from '@strapi/helper-plugin';
|
||||
|
||||
const MyComponent = () => {
|
||||
const { locale } = useIntl();
|
||||
|
||||
const fruits = ['orange', 'apple', 'kiwi', 'banana'];
|
||||
const searchValue = 'an';
|
||||
|
||||
const { contains } = useFilter(locale, { sensitivity: 'base' });
|
||||
|
||||
/**
|
||||
* This would render the list of fruits in the following order:
|
||||
* orange
|
||||
* banana
|
||||
*/
|
||||
return (
|
||||
<div>
|
||||
{fruits
|
||||
.filter((fruit) => includes(fruit, searchValue))
|
||||
.map((fruit) => (
|
||||
<h2 key={fruit}>{fruit}</h2>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Typescript
|
||||
|
||||
```ts
|
||||
interface Filter {
|
||||
/** Returns whether a string starts with a given substring. */
|
||||
startsWith(string: string, substring: string): boolean;
|
||||
/** Returns whether a string ends with a given substring. */
|
||||
endsWith(string: string, substring: string): boolean;
|
||||
/** Returns whether a string contains a given substring. */
|
||||
includes(string: string, substring: string): boolean;
|
||||
}
|
||||
|
||||
function useFilter(locale: string, options?: Intl.CollatorOptions): Filter;
|
||||
```
|
||||
@ -115,16 +115,25 @@ const sidebars = {
|
||||
type: 'category',
|
||||
label: 'Hooks',
|
||||
items: [
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'useAPIErrorHandler',
|
||||
id: 'core/helper-plugin/hooks/use-api-error-handler',
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'useCollator',
|
||||
id: 'core/helper-plugin/hooks/use-collator',
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'useFetchClient',
|
||||
id: 'core/helper-plugin/hooks/use-fetch-client',
|
||||
},
|
||||
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'useAPIErrorHandler',
|
||||
id: 'core/helper-plugin/hooks/use-api-error-handler',
|
||||
label: 'useFilter',
|
||||
id: 'core/helper-plugin/hooks/use-filter',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
19385
docs/yarn.lock
19385
docs/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -27,7 +27,7 @@ const LeftMenu = () => {
|
||||
const modelLinksSelector = useMemo(makeSelectModelLinks, []);
|
||||
const { collectionTypeLinks, singleTypeLinks } = useSelector(modelLinksSelector, shallowEqual);
|
||||
|
||||
const { contains } = useFilter(locale, {
|
||||
const { includes } = useFilter(locale, {
|
||||
sensitivity: 'base',
|
||||
});
|
||||
|
||||
@ -65,7 +65,7 @@ const LeftMenu = () => {
|
||||
/**
|
||||
* Filter by the search value
|
||||
*/
|
||||
.filter((link) => contains(link.title, search))
|
||||
.filter((link) => includes(link.title, search))
|
||||
/**
|
||||
* Sort correctly using the language
|
||||
*/
|
||||
@ -80,7 +80,7 @@ const LeftMenu = () => {
|
||||
};
|
||||
}),
|
||||
})),
|
||||
[collectionTypeLinks, search, singleTypeLinks, contains, formatMessage, formatter]
|
||||
[collectionTypeLinks, search, singleTypeLinks, includes, formatMessage, formatter]
|
||||
);
|
||||
|
||||
const handleClear = () => {
|
||||
|
||||
@ -49,7 +49,7 @@ const useSortedRoles = () => {
|
||||
const [{ query }] = useQueryParams();
|
||||
const _q = query?._q || '';
|
||||
|
||||
const { contains } = useFilter(locale, {
|
||||
const { includes } = useFilter(locale, {
|
||||
sensitivity: 'base',
|
||||
});
|
||||
|
||||
@ -61,7 +61,7 @@ const useSortedRoles = () => {
|
||||
});
|
||||
|
||||
const sortedRoles = (roles || [])
|
||||
.filter((role) => contains(role.name, _q) || contains(role.description, _q))
|
||||
.filter((role) => includes(role.name, _q) || includes(role.description, _q))
|
||||
.sort(
|
||||
(a, b) => formatter.compare(a.name, b.name) || formatter.compare(a.description, b.description)
|
||||
);
|
||||
|
||||
@ -25,7 +25,7 @@ const useContentTypeBuilderMenu = () => {
|
||||
const { onOpenModalCreateSchema, onOpenModalEditCategory } = useFormModalNavigation();
|
||||
const { locale } = useIntl();
|
||||
|
||||
const { contains } = useFilter(locale, {
|
||||
const { includes } = useFilter(locale, {
|
||||
sensitivity: 'base',
|
||||
});
|
||||
|
||||
@ -117,10 +117,10 @@ const useContentTypeBuilderMenu = () => {
|
||||
}
|
||||
},
|
||||
links: components
|
||||
.map((compo) => ({
|
||||
name: compo.uid,
|
||||
to: `/plugins/${pluginId}/component-categories/${category}/${compo.uid}`,
|
||||
title: compo.schema.displayName,
|
||||
.map((component) => ({
|
||||
name: component.uid,
|
||||
to: `/plugins/${pluginId}/component-categories/${category}/${component.uid}`,
|
||||
title: component.schema.displayName,
|
||||
}))
|
||||
.sort((a, b) => formatter.compare(a.title, b.title)),
|
||||
}))
|
||||
@ -177,7 +177,7 @@ const useContentTypeBuilderMenu = () => {
|
||||
links: section.links.map((link) => ({
|
||||
...link,
|
||||
links: link.links
|
||||
.filter((link) => contains(link.title, search))
|
||||
.filter((link) => includes(link.title, search))
|
||||
.sort((a, b) => formatter.compare(a.title, b.title)),
|
||||
})),
|
||||
};
|
||||
@ -186,7 +186,7 @@ const useContentTypeBuilderMenu = () => {
|
||||
return {
|
||||
...section,
|
||||
links: section.links
|
||||
.filter((link) => contains(link.title, search))
|
||||
.filter((link) => includes(link.title, search))
|
||||
.sort((a, b) => formatter.compare(a.title, b.title)),
|
||||
};
|
||||
});
|
||||
|
||||
@ -34,7 +34,7 @@ const HomePage = () => {
|
||||
const items = [{name: 'Paul', instrument: 'bass'}, {name: 'George', instrument: 'guitar'}]
|
||||
|
||||
|
||||
const { contains } = useFilter(locale, {
|
||||
const { includes } = useFilter(locale, {
|
||||
sensitivity: 'base',
|
||||
});
|
||||
|
||||
@ -44,7 +44,7 @@ const HomePage = () => {
|
||||
|
||||
|
||||
const sortedList = items
|
||||
.filter((item) => contains(item.name, _q) || contains(item.instrument, _q))
|
||||
.filter((item) => includes(item.name, _q) || includes(item.instrument, _q))
|
||||
.sort(
|
||||
(a, b) => formatter.compare(a.name, b.name) || formatter.compare(a.instrument, b.instrument)
|
||||
);
|
||||
@ -83,14 +83,14 @@ const HomePage = () => {
|
||||
{ name: 'George', instrument: 'guitar' },
|
||||
];
|
||||
const { locale } = useIntl();
|
||||
const { contains } = useFilter(locale, {
|
||||
const { includes } = useFilter(locale, {
|
||||
sensitivity: 'base',
|
||||
});
|
||||
const formatter = useCollator(locale, {
|
||||
sensitivity: 'base',
|
||||
});
|
||||
const sortedList = items
|
||||
.filter((item) => contains(item.name, _q) || contains(item.instrument, _q))
|
||||
.filter((item) => includes(item.name, _q) || includes(item.instrument, _q))
|
||||
.sort(
|
||||
(a, b) =>
|
||||
formatter.compare(a.name, b.name) || formatter.compare(a.instrument, b.instrument)
|
||||
|
||||
@ -0,0 +1,54 @@
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
|
||||
import { useCollator } from '../useCollator';
|
||||
|
||||
describe('useCollator', () => {
|
||||
it('should return the Intl.Collator class', () => {
|
||||
const { result } = renderHook(() => useCollator('en'));
|
||||
|
||||
expect(result.current).toBeInstanceOf(Intl.Collator);
|
||||
});
|
||||
|
||||
it('should pass options to the Intl.Collator class if I pass them to the hook', () => {
|
||||
const { result } = renderHook(() => useCollator('en', { sensitivity: 'base' }));
|
||||
|
||||
expect(result.current.resolvedOptions().sensitivity).toBe('base');
|
||||
});
|
||||
|
||||
it('should return me a new Intl.Collator if I pass a new locale or options object', () => {
|
||||
const { result, rerender } = renderHook(({ locale, options }) => useCollator(locale, options), {
|
||||
initialProps: {
|
||||
locale: 'en',
|
||||
options: { sensitivity: 'base' },
|
||||
},
|
||||
});
|
||||
|
||||
const first = result.current;
|
||||
|
||||
rerender({
|
||||
locale: 'en',
|
||||
options: { sensitivity: 'accent' },
|
||||
});
|
||||
|
||||
const second = result.current;
|
||||
|
||||
expect(second).not.toBe(first);
|
||||
|
||||
rerender({
|
||||
locale: 'fr',
|
||||
options: { sensitivity: 'accent' },
|
||||
});
|
||||
|
||||
const third = result.current;
|
||||
|
||||
expect(third).not.toBe(second);
|
||||
expect(third).not.toBe(first);
|
||||
|
||||
rerender({
|
||||
locale: 'en',
|
||||
options: { sensitivity: 'base' },
|
||||
});
|
||||
|
||||
expect(result.current).toBe(first);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,73 @@
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
|
||||
import { useFilter } from '../useFilter';
|
||||
|
||||
describe('useFilter', () => {
|
||||
it("should return an object with the properties 'startsWith', 'endsWith, & 'includes'", () => {
|
||||
const { result } = renderHook(() => useFilter('en'));
|
||||
|
||||
expect(result.current).toHaveProperty('startsWith');
|
||||
expect(result.current).toHaveProperty('endsWith');
|
||||
expect(result.current).toHaveProperty('includes');
|
||||
});
|
||||
|
||||
describe('startsWith', () => {
|
||||
it('should return true if the substring is empty', () => {
|
||||
const { result } = renderHook(() => useFilter('en'));
|
||||
|
||||
expect(result.current.startsWith('foo', '')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if the string starts with the substring', () => {
|
||||
const { result } = renderHook(() => useFilter('en'));
|
||||
|
||||
expect(result.current.startsWith('foo', 'f')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the string does not start with the substring', () => {
|
||||
const { result } = renderHook(() => useFilter('en'));
|
||||
|
||||
expect(result.current.startsWith('foo', 'o')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('endsWith', () => {
|
||||
it('should return true if the substring is empty', () => {
|
||||
const { result } = renderHook(() => useFilter('en'));
|
||||
|
||||
expect(result.current.endsWith('foo', '')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if the string ends with the substring', () => {
|
||||
const { result } = renderHook(() => useFilter('en'));
|
||||
|
||||
expect(result.current.endsWith('foo', 'o')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the string does not end with the substring', () => {
|
||||
const { result } = renderHook(() => useFilter('en'));
|
||||
|
||||
expect(result.current.endsWith('foo', 'f')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('includes', () => {
|
||||
it('should return true if the substring is empty', () => {
|
||||
const { result } = renderHook(() => useFilter('en'));
|
||||
|
||||
expect(result.current.includes('foo', '')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if the string includes the substring', () => {
|
||||
const { result } = renderHook(() => useFilter('en'));
|
||||
|
||||
expect(result.current.includes('foo', 'o')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the string does not include the substring', () => {
|
||||
const { result } = renderHook(() => useFilter('en'));
|
||||
|
||||
expect(result.current.includes('foo', 'b')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -8,7 +8,7 @@ import { useCollator } from './useCollator';
|
||||
* @typedef {Object} Filter
|
||||
* @property {(string: string, substring: string) => boolean} startsWith – Returns whether a string starts with a given substring.
|
||||
* @property {(string: string, substring: string) => boolean} endsWith – Returns whether a string ends with a given substring.
|
||||
* @property {(string: string, substring: string) => boolean} contains – Returns whether a string contains a given substring.
|
||||
* @property {(string: string, substring: string) => boolean} includes – Returns whether a string contains a given substring.
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -45,7 +45,7 @@ export function useFilter(locale, options) {
|
||||
|
||||
return collator.compare(string.slice(-substring.length), substring) === 0;
|
||||
},
|
||||
contains(string, substring) {
|
||||
includes(string, substring) {
|
||||
if (substring.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ const RoleListPage = () => {
|
||||
enabled: canRead,
|
||||
});
|
||||
|
||||
const { contains } = useFilter(locale, {
|
||||
const { includes } = useFilter(locale, {
|
||||
sensitivity: 'base',
|
||||
});
|
||||
|
||||
@ -131,7 +131,7 @@ const RoleListPage = () => {
|
||||
};
|
||||
|
||||
const sortedRoles = (roles || [])
|
||||
.filter((role) => contains(role.name, _q) || contains(role.description, _q))
|
||||
.filter((role) => includes(role.name, _q) || includes(role.description, _q))
|
||||
.sort(
|
||||
(a, b) => formatter.compare(a.name, b.name) || formatter.compare(a.description, b.description)
|
||||
);
|
||||
|
||||
14
yarn.lock
14
yarn.lock
@ -7046,7 +7046,6 @@ __metadata:
|
||||
markdown-it-mark: ^3.0.1
|
||||
markdown-it-sub: ^1.0.0
|
||||
markdown-it-sup: 1.0.0
|
||||
match-sorter: ^4.0.2
|
||||
mini-css-extract-plugin: 2.7.2
|
||||
msw: 1.0.1
|
||||
node-schedule: 2.1.0
|
||||
@ -7264,7 +7263,6 @@ __metadata:
|
||||
history: ^4.9.0
|
||||
immer: 9.0.19
|
||||
lodash: 4.17.21
|
||||
match-sorter: ^4.0.2
|
||||
prop-types: ^15.7.2
|
||||
qs: 6.11.1
|
||||
react: ^17.0.2
|
||||
@ -7383,7 +7381,6 @@ __metadata:
|
||||
history: ^4.9.0
|
||||
immer: 9.0.19
|
||||
lodash: 4.17.21
|
||||
match-sorter: ^4.0.2
|
||||
pluralize: ^8.0.0
|
||||
prop-types: ^15.7.2
|
||||
qs: 6.11.1
|
||||
@ -7635,7 +7632,6 @@ __metadata:
|
||||
koa: ^2.13.4
|
||||
koa2-ratelimit: ^1.1.2
|
||||
lodash: 4.17.21
|
||||
match-sorter: ^4.0.2
|
||||
msw: 1.0.1
|
||||
prop-types: ^15.7.2
|
||||
purest: 4.0.2
|
||||
@ -22250,16 +22246,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"match-sorter@npm:^4.0.2":
|
||||
version: 4.2.1
|
||||
resolution: "match-sorter@npm:4.2.1"
|
||||
dependencies:
|
||||
"@babel/runtime": ^7.10.5
|
||||
remove-accents: 0.4.2
|
||||
checksum: 7f3cd8f84cdb4567b7a81f66a095e418044f318f41b6c8a1640730c99e370af9e5054f5a1ed2dfa1287a23101d75a105da63e4d95e8ac2f7061a8f8b32367c7d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"match-sorter@npm:^6.0.2":
|
||||
version: 6.3.1
|
||||
resolution: "match-sorter@npm:6.3.1"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user