858 lines
24 KiB
TypeScript
858 lines
24 KiB
TypeScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
import path from 'path';
|
|
import fs from 'fs-extra';
|
|
import {
|
|
fileToPath,
|
|
simpleHash,
|
|
docuHash,
|
|
genComponentName,
|
|
genChunkName,
|
|
idx,
|
|
getSubFolder,
|
|
normalizeUrl,
|
|
posixPath,
|
|
objectWithKeySorted,
|
|
aliasedSitePath,
|
|
createExcerpt,
|
|
isValidPathname,
|
|
addTrailingSlash,
|
|
removeTrailingSlash,
|
|
removeSuffix,
|
|
removePrefix,
|
|
getFilePathForRoutePath,
|
|
addLeadingSlash,
|
|
getElementsAround,
|
|
mergeTranslations,
|
|
mapAsyncSequencial,
|
|
findAsyncSequential,
|
|
findFolderContainingFile,
|
|
getFolderContainingFile,
|
|
updateTranslationFileMessages,
|
|
readDefaultCodeTranslationMessages,
|
|
parseMarkdownHeadingId,
|
|
} from '../index';
|
|
import {sum} from 'lodash';
|
|
|
|
describe('load utils', () => {
|
|
test('aliasedSitePath', () => {
|
|
const asserts = {
|
|
'user/website/docs/asd.md': '@site/docs/asd.md',
|
|
'user/website/versioned_docs/foo/bar.md':
|
|
'@site/versioned_docs/foo/bar.md',
|
|
'user/docs/test.md': '@site/../docs/test.md',
|
|
};
|
|
Object.keys(asserts).forEach((file) => {
|
|
expect(posixPath(aliasedSitePath(file, 'user/website'))).toBe(
|
|
asserts[file],
|
|
);
|
|
});
|
|
});
|
|
|
|
test('posixPath', () => {
|
|
const asserts = {
|
|
'c:/aaaa\\bbbb': 'c:/aaaa/bbbb',
|
|
'c:\\aaaa\\bbbb\\★': 'c:\\aaaa\\bbbb\\★',
|
|
'\\\\?\\c:\\aaaa\\bbbb': '\\\\?\\c:\\aaaa\\bbbb',
|
|
'c:\\aaaa\\bbbb': 'c:/aaaa/bbbb',
|
|
'foo\\bar': 'foo/bar',
|
|
'foo\\bar/lol': 'foo/bar/lol',
|
|
'website\\docs/**/*.{md,mdx}': 'website/docs/**/*.{md,mdx}',
|
|
};
|
|
Object.keys(asserts).forEach((file) => {
|
|
expect(posixPath(file)).toBe(asserts[file]);
|
|
});
|
|
});
|
|
|
|
test('genComponentName', () => {
|
|
const asserts = {
|
|
'/': 'index',
|
|
'/foo-bar': 'FooBar096',
|
|
'/foo/bar': 'FooBar1Df',
|
|
'/blog/2017/12/14/introducing-docusaurus':
|
|
'Blog20171214IntroducingDocusaurus8D2',
|
|
'/blog/2017/12/14-introducing-docusaurus':
|
|
'Blog20171214IntroducingDocusaurus0Bc',
|
|
'/blog/201712/14-introducing-docusaurus':
|
|
'Blog20171214IntroducingDocusaurusA93',
|
|
};
|
|
Object.keys(asserts).forEach((file) => {
|
|
expect(genComponentName(file)).toBe(asserts[file]);
|
|
});
|
|
});
|
|
|
|
test('simpleHash', () => {
|
|
const asserts = {
|
|
'': 'd41',
|
|
'/foo-bar': '096',
|
|
'/foo/bar': '1df',
|
|
'/endi/lie': '9fa',
|
|
'/endi-lie': 'fd3',
|
|
'/yangshun/tay': '48d',
|
|
'/yangshun-tay': 'f3b',
|
|
};
|
|
Object.keys(asserts).forEach((file) => {
|
|
expect(simpleHash(file, 3)).toBe(asserts[file]);
|
|
});
|
|
});
|
|
|
|
test('docuHash', () => {
|
|
const asserts = {
|
|
'': '-d41',
|
|
'/': 'index',
|
|
'/foo-bar': 'foo-bar-096',
|
|
'/foo/bar': 'foo-bar-1df',
|
|
'/endi/lie': 'endi-lie-9fa',
|
|
'/endi-lie': 'endi-lie-fd3',
|
|
'/yangshun/tay': 'yangshun-tay-48d',
|
|
'/yangshun-tay': 'yangshun-tay-f3b',
|
|
};
|
|
Object.keys(asserts).forEach((file) => {
|
|
expect(docuHash(file)).toBe(asserts[file]);
|
|
});
|
|
});
|
|
|
|
test('fileToPath', () => {
|
|
const asserts = {
|
|
'index.md': '/',
|
|
'hello/index.md': '/hello/',
|
|
'foo.md': '/foo',
|
|
'foo/bar.md': '/foo/bar',
|
|
'index.js': '/',
|
|
'hello/index.js': '/hello/',
|
|
'foo.js': '/foo',
|
|
'foo/bar.js': '/foo/bar',
|
|
};
|
|
Object.keys(asserts).forEach((file) => {
|
|
expect(fileToPath(file)).toBe(asserts[file]);
|
|
});
|
|
});
|
|
|
|
test('objectWithKeySorted', () => {
|
|
const obj = {
|
|
'/docs/adding-blog': '4',
|
|
'/docs/versioning': '5',
|
|
'/': '1',
|
|
'/blog/2018': '3',
|
|
'/youtube': '7',
|
|
'/users/en/': '6',
|
|
'/blog': '2',
|
|
};
|
|
expect(objectWithKeySorted(obj)).toMatchInlineSnapshot(`
|
|
Object {
|
|
"/": "1",
|
|
"/blog": "2",
|
|
"/blog/2018": "3",
|
|
"/docs/adding-blog": "4",
|
|
"/docs/versioning": "5",
|
|
"/users/en/": "6",
|
|
"/youtube": "7",
|
|
}
|
|
`);
|
|
const obj2 = {
|
|
b: 'foo',
|
|
c: 'bar',
|
|
a: 'baz',
|
|
};
|
|
expect(objectWithKeySorted(obj2)).toMatchInlineSnapshot(`
|
|
Object {
|
|
"a": "baz",
|
|
"b": "foo",
|
|
"c": "bar",
|
|
}
|
|
`);
|
|
});
|
|
|
|
test('genChunkName', () => {
|
|
const firstAssert = {
|
|
'/docs/adding-blog': 'docs-adding-blog-062',
|
|
'/docs/versioning': 'docs-versioning-8a8',
|
|
'/': 'index',
|
|
'/blog/2018/04/30/How-I-Converted-Profilo-To-Docusaurus':
|
|
'blog-2018-04-30-how-i-converted-profilo-to-docusaurus-4f2',
|
|
'/youtube': 'youtube-429',
|
|
'/users/en/': 'users-en-f7a',
|
|
'/blog': 'blog-c06',
|
|
};
|
|
Object.keys(firstAssert).forEach((str) => {
|
|
expect(genChunkName(str)).toBe(firstAssert[str]);
|
|
});
|
|
|
|
// Don't allow different chunk name for same path.
|
|
expect(genChunkName('path/is/similar', 'oldPrefix')).toEqual(
|
|
genChunkName('path/is/similar', 'newPrefix'),
|
|
);
|
|
|
|
// Even with same preferred name, still different chunk name for different path
|
|
const secondAssert = {
|
|
'/blog/1': 'blog-85-f-089',
|
|
'/blog/2': 'blog-353-489',
|
|
};
|
|
Object.keys(secondAssert).forEach((str) => {
|
|
expect(genChunkName(str, undefined, 'blog')).toBe(secondAssert[str]);
|
|
});
|
|
|
|
// Only generate short unique id
|
|
const thirdAssert = {
|
|
a: '0cc175b9',
|
|
b: '92eb5ffe',
|
|
c: '4a8a08f0',
|
|
d: '8277e091',
|
|
};
|
|
Object.keys(thirdAssert).forEach((str) => {
|
|
expect(genChunkName(str, undefined, undefined, true)).toBe(
|
|
thirdAssert[str],
|
|
);
|
|
});
|
|
expect(genChunkName('d', undefined, undefined, true)).toBe('8277e091');
|
|
});
|
|
|
|
test('idx', () => {
|
|
const a = {};
|
|
const b = {hello: 'world'};
|
|
const obj = {
|
|
translation: {
|
|
enabled: true,
|
|
enabledLanguages: [
|
|
{
|
|
enabled: true,
|
|
name: 'English',
|
|
tag: 'en',
|
|
},
|
|
{
|
|
enabled: true,
|
|
name: '日本語',
|
|
tag: 'ja',
|
|
},
|
|
],
|
|
},
|
|
versioning: {
|
|
enabled: false,
|
|
versions: [],
|
|
},
|
|
};
|
|
const test = {arr: [1, 2, 3]};
|
|
const variable = 'enabledLanguages';
|
|
expect(idx(a, ['b', 'c'])).toBeUndefined();
|
|
expect(idx(b, ['hello'])).toEqual('world');
|
|
expect(idx(b, 'hello')).toEqual('world');
|
|
expect(idx(obj, 'typo')).toBeUndefined();
|
|
expect(idx(obj, 'versioning')).toEqual({
|
|
enabled: false,
|
|
versions: [],
|
|
});
|
|
expect(idx(obj, ['translation', 'enabled'])).toEqual(true);
|
|
expect(
|
|
idx(obj, ['translation', variable]).map((lang) => lang.tag),
|
|
).toEqual(['en', 'ja']);
|
|
expect(idx(test, ['arr', 0])).toEqual(1);
|
|
expect(idx(undefined)).toBeUndefined();
|
|
expect(idx(null)).toBeNull();
|
|
});
|
|
|
|
test('getSubFolder', () => {
|
|
const testA = path.join('folder', 'en', 'test.md');
|
|
const testB = path.join('folder', 'ja', 'test.md');
|
|
const testC = path.join('folder', 'ja', 'en', 'test.md');
|
|
const testD = path.join('docs', 'ro', 'test.md');
|
|
const testE = path.join('docs', 'test.md');
|
|
expect(getSubFolder(testA, 'folder')).toBe('en');
|
|
expect(getSubFolder(testB, 'folder')).toBe('ja');
|
|
expect(getSubFolder(testC, 'folder')).toBe('ja');
|
|
expect(getSubFolder(testD, 'docs')).toBe('ro');
|
|
expect(getSubFolder(testE, 'docs')).toBeNull();
|
|
});
|
|
|
|
test('normalizeUrl', () => {
|
|
const asserts = [
|
|
{
|
|
input: ['/', ''],
|
|
output: '/',
|
|
},
|
|
{
|
|
input: ['', '/'],
|
|
output: '/',
|
|
},
|
|
{
|
|
input: ['/'],
|
|
output: '/',
|
|
},
|
|
{
|
|
input: [''],
|
|
output: '',
|
|
},
|
|
{
|
|
input: ['/', '/'],
|
|
output: '/',
|
|
},
|
|
{
|
|
input: ['/', 'docs'],
|
|
output: '/docs',
|
|
},
|
|
{
|
|
input: ['/', 'docs', 'en', 'next', 'blog'],
|
|
output: '/docs/en/next/blog',
|
|
},
|
|
{
|
|
input: ['/test/', '/docs', 'ro', 'doc1'],
|
|
output: '/test/docs/ro/doc1',
|
|
},
|
|
{
|
|
input: ['/test/', '/', 'ro', 'doc1'],
|
|
output: '/test/ro/doc1',
|
|
},
|
|
{
|
|
input: ['/', '/', '2020/02/29/leap-day'],
|
|
output: '/2020/02/29/leap-day',
|
|
},
|
|
{
|
|
input: ['', '/', 'ko', 'hello'],
|
|
output: '/ko/hello',
|
|
},
|
|
{
|
|
input: ['hello', 'world'],
|
|
output: 'hello/world',
|
|
},
|
|
{
|
|
input: ['http://www.google.com/', 'foo/bar', '?test=123'],
|
|
output: 'http://www.google.com/foo/bar?test=123',
|
|
},
|
|
{
|
|
input: ['http:', 'www.google.com///', 'foo/bar', '?test=123'],
|
|
output: 'http://www.google.com/foo/bar?test=123',
|
|
},
|
|
{
|
|
input: ['http://foobar.com', '', 'test'],
|
|
output: 'http://foobar.com/test',
|
|
},
|
|
{
|
|
input: ['http://foobar.com', '', 'test', '/'],
|
|
output: 'http://foobar.com/test/',
|
|
},
|
|
{
|
|
input: ['/', '', 'hello', '', '/', '/', '', '/', '/world'],
|
|
output: '/hello/world',
|
|
},
|
|
{
|
|
input: ['', '', '/tt', 'ko', 'hello'],
|
|
output: '/tt/ko/hello',
|
|
},
|
|
{
|
|
input: ['', '///hello///', '', '///world'],
|
|
output: '/hello/world',
|
|
},
|
|
{
|
|
input: ['', '/hello/', ''],
|
|
output: '/hello/',
|
|
},
|
|
{
|
|
input: ['', '/', ''],
|
|
output: '/',
|
|
},
|
|
{
|
|
input: ['///', '///'],
|
|
output: '/',
|
|
},
|
|
{
|
|
input: ['/', '/hello/world/', '///'],
|
|
output: '/hello/world/',
|
|
},
|
|
];
|
|
asserts.forEach((testCase) => {
|
|
expect(normalizeUrl(testCase.input)).toBe(testCase.output);
|
|
});
|
|
|
|
expect(() =>
|
|
normalizeUrl(['http:example.com', undefined]),
|
|
).toThrowErrorMatchingInlineSnapshot(
|
|
`"Url must be a string. Received undefined"`,
|
|
);
|
|
});
|
|
|
|
test('createExcerpt', () => {
|
|
const asserts = [
|
|
// Regular content
|
|
{
|
|
input: `
|
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ex urna, molestie et sagittis ut, varius ac justo.
|
|
|
|
Nunc porttitor libero nec vulputate venenatis. Nam nec rhoncus mauris. Morbi tempus est et nibh maximus, tempus venenatis arcu lobortis.
|
|
`,
|
|
output:
|
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ex urna, molestie et sagittis ut, varius ac justo.',
|
|
},
|
|
// Content with imports/exports declarations and Markdown markup, as well as Emoji
|
|
{
|
|
input: `
|
|
import Component from '@site/src/components/Component';
|
|
import Component from '@site/src/components/Component'
|
|
import './styles.css';
|
|
|
|
export function ItemCol(props) { return <Item {...props} className={'col col--6 margin-bottom--lg'}/> }
|
|
|
|
export function ItemCol(props) { return <Item {...props} className={'col col--6 margin-bottom--lg'}/> };
|
|
|
|
Lorem **ipsum** dolor sit \`amet\`[^1], consectetur _adipiscing_ elit. [**Vestibulum**](https://wiktionary.org/wiki/vestibulum) ex urna[^bignote], ~molestie~ et sagittis ut, varius ac justo :wink:.
|
|
|
|
Nunc porttitor libero nec vulputate venenatis. Nam nec rhoncus mauris. Morbi tempus est et nibh maximus, tempus venenatis arcu lobortis.
|
|
`,
|
|
output:
|
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ex urna, molestie et sagittis ut, varius ac justo.',
|
|
},
|
|
// Content beginning with admonitions
|
|
{
|
|
input: `
|
|
import Component from '@site/src/components/Component'
|
|
|
|
:::caution
|
|
|
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
|
|
|
:::
|
|
|
|
Nunc porttitor libero nec vulputate venenatis. Nam nec rhoncus mauris. Morbi tempus est et nibh maximus, tempus venenatis arcu lobortis.
|
|
`,
|
|
output: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
|
},
|
|
// Content beginning with heading
|
|
{
|
|
input: `
|
|
## Lorem ipsum dolor sit amet
|
|
|
|
Nunc porttitor libero nec vulputate venenatis. Nam nec rhoncus mauris. Morbi tempus est et nibh maximus, tempus venenatis arcu lobortis.
|
|
`,
|
|
output: 'Lorem ipsum dolor sit amet',
|
|
},
|
|
// Content beginning with blockquote
|
|
{
|
|
input: `
|
|
> Lorem ipsum dolor sit amet
|
|
`,
|
|
output: 'Lorem ipsum dolor sit amet',
|
|
},
|
|
// Content beginning with image (eg. blog post)
|
|
{
|
|
input: `
|
|

|
|
`,
|
|
output: 'Lorem ipsum',
|
|
},
|
|
];
|
|
|
|
asserts.forEach((testCase) => {
|
|
expect(createExcerpt(testCase.input)).toEqual(testCase.output);
|
|
});
|
|
});
|
|
|
|
test('isValidPathname', () => {
|
|
expect(isValidPathname('/')).toBe(true);
|
|
expect(isValidPathname('/hey')).toBe(true);
|
|
expect(isValidPathname('/hey/ho')).toBe(true);
|
|
expect(isValidPathname('/hey/ho/')).toBe(true);
|
|
expect(isValidPathname('/hey/h%C3%B4/')).toBe(true);
|
|
expect(isValidPathname('/hey///ho///')).toBe(true); // Unexpected but valid
|
|
expect(isValidPathname('/hey/héllô you')).toBe(true);
|
|
|
|
//
|
|
expect(isValidPathname('')).toBe(false);
|
|
expect(isValidPathname('hey')).toBe(false);
|
|
expect(isValidPathname('/hey?qs=ho')).toBe(false);
|
|
expect(isValidPathname('https://fb.com/hey')).toBe(false);
|
|
expect(isValidPathname('//hey')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('addTrailingSlash', () => {
|
|
test('should no-op', () => {
|
|
expect(addTrailingSlash('/abcd/')).toEqual('/abcd/');
|
|
});
|
|
test('should add /', () => {
|
|
expect(addTrailingSlash('/abcd')).toEqual('/abcd/');
|
|
});
|
|
});
|
|
|
|
describe('addLeadingSlash', () => {
|
|
test('should no-op', () => {
|
|
expect(addLeadingSlash('/abc')).toEqual('/abc');
|
|
});
|
|
test('should add /', () => {
|
|
expect(addLeadingSlash('abc')).toEqual('/abc');
|
|
});
|
|
});
|
|
|
|
describe('removeTrailingSlash', () => {
|
|
test('should no-op', () => {
|
|
expect(removeTrailingSlash('/abcd')).toEqual('/abcd');
|
|
});
|
|
test('should remove /', () => {
|
|
expect(removeTrailingSlash('/abcd/')).toEqual('/abcd');
|
|
});
|
|
});
|
|
|
|
describe('removeSuffix', () => {
|
|
test('should no-op 1', () => {
|
|
expect(removeSuffix('abcdef', 'ijk')).toEqual('abcdef');
|
|
});
|
|
test('should no-op 2', () => {
|
|
expect(removeSuffix('abcdef', 'abc')).toEqual('abcdef');
|
|
});
|
|
test('should no-op 3', () => {
|
|
expect(removeSuffix('abcdef', '')).toEqual('abcdef');
|
|
});
|
|
test('should remove suffix', () => {
|
|
expect(removeSuffix('abcdef', 'ef')).toEqual('abcd');
|
|
});
|
|
});
|
|
|
|
describe('removePrefix', () => {
|
|
test('should no-op 1', () => {
|
|
expect(removePrefix('abcdef', 'ijk')).toEqual('abcdef');
|
|
});
|
|
test('should no-op 2', () => {
|
|
expect(removePrefix('abcdef', 'def')).toEqual('abcdef');
|
|
});
|
|
test('should no-op 3', () => {
|
|
expect(removePrefix('abcdef', '')).toEqual('abcdef');
|
|
});
|
|
test('should remove prefix', () => {
|
|
expect(removePrefix('abcdef', 'ab')).toEqual('cdef');
|
|
});
|
|
});
|
|
|
|
describe('getFilePathForRoutePath', () => {
|
|
test('works for /', () => {
|
|
expect(posixPath(getFilePathForRoutePath('/'))).toEqual('/index.html');
|
|
});
|
|
test('works for /somePath', () => {
|
|
expect(posixPath(getFilePathForRoutePath('/somePath'))).toEqual(
|
|
'/somePath/index.html',
|
|
);
|
|
});
|
|
test('works for /somePath/', () => {
|
|
expect(posixPath(getFilePathForRoutePath('/somePath/'))).toEqual(
|
|
'/somePath/index.html',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('getElementsAround', () => {
|
|
test('can return elements around', () => {
|
|
expect(getElementsAround(['a', 'b', 'c', 'd'], 0)).toEqual({
|
|
previous: undefined,
|
|
next: 'b',
|
|
});
|
|
expect(getElementsAround(['a', 'b', 'c', 'd'], 1)).toEqual({
|
|
previous: 'a',
|
|
next: 'c',
|
|
});
|
|
expect(getElementsAround(['a', 'b', 'c', 'd'], 2)).toEqual({
|
|
previous: 'b',
|
|
next: 'd',
|
|
});
|
|
expect(getElementsAround(['a', 'b', 'c', 'd'], 3)).toEqual({
|
|
previous: 'c',
|
|
next: undefined,
|
|
});
|
|
});
|
|
|
|
test('throws if bad index is provided', () => {
|
|
expect(() =>
|
|
getElementsAround(['a', 'b', 'c', 'd'], -1),
|
|
).toThrowErrorMatchingInlineSnapshot(
|
|
`"Valid aroundIndex for array (of size 4) are between 0 and 3, but you provided aroundIndex=-1"`,
|
|
);
|
|
expect(() =>
|
|
getElementsAround(['a', 'b', 'c', 'd'], 4),
|
|
).toThrowErrorMatchingInlineSnapshot(
|
|
`"Valid aroundIndex for array (of size 4) are between 0 and 3, but you provided aroundIndex=4"`,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('mergeTranslations', () => {
|
|
test('should merge translations', () => {
|
|
expect(
|
|
mergeTranslations([
|
|
{
|
|
T1: {message: 'T1 message', description: 'T1 desc'},
|
|
T2: {message: 'T2 message', description: 'T2 desc'},
|
|
T3: {message: 'T3 message', description: 'T3 desc'},
|
|
},
|
|
{
|
|
T4: {message: 'T4 message', description: 'T4 desc'},
|
|
},
|
|
{T2: {message: 'T2 message 2', description: 'T2 desc 2'}},
|
|
]),
|
|
).toEqual({
|
|
T1: {message: 'T1 message', description: 'T1 desc'},
|
|
T2: {message: 'T2 message 2', description: 'T2 desc 2'},
|
|
T3: {message: 'T3 message', description: 'T3 desc'},
|
|
T4: {message: 'T4 message', description: 'T4 desc'},
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('mapAsyncSequencial', () => {
|
|
function sleep(timeout: number): Promise<void> {
|
|
return new Promise((resolve) => setTimeout(resolve, timeout));
|
|
}
|
|
|
|
test('map sequentially', async () => {
|
|
const itemToTimeout: Record<string, number> = {
|
|
'1': 50,
|
|
'2': 150,
|
|
'3': 100,
|
|
};
|
|
const items = Object.keys(itemToTimeout);
|
|
|
|
const itemMapStartsAt: Record<string, number> = {};
|
|
const itemMapEndsAt: Record<string, number> = {};
|
|
|
|
const timeBefore = Date.now();
|
|
await expect(
|
|
mapAsyncSequencial(items, async (item) => {
|
|
const itemTimeout = itemToTimeout[item];
|
|
itemMapStartsAt[item] = Date.now();
|
|
await sleep(itemTimeout);
|
|
itemMapEndsAt[item] = Date.now();
|
|
return `${item} mapped`;
|
|
}),
|
|
).resolves.toEqual(['1 mapped', '2 mapped', '3 mapped']);
|
|
const timeAfter = Date.now();
|
|
|
|
const timeTotal = timeAfter - timeBefore;
|
|
|
|
const totalTimeouts = sum(Object.values(itemToTimeout));
|
|
expect(timeTotal > totalTimeouts);
|
|
|
|
expect(itemMapStartsAt['1'] > 0);
|
|
expect(itemMapStartsAt['2'] > itemMapEndsAt['1']);
|
|
expect(itemMapStartsAt['3'] > itemMapEndsAt['2']);
|
|
});
|
|
});
|
|
|
|
describe('findAsyncSequencial', () => {
|
|
function sleep(timeout: number): Promise<void> {
|
|
return new Promise((resolve) => setTimeout(resolve, timeout));
|
|
}
|
|
|
|
test('find sequentially', async () => {
|
|
const items = ['1', '2', '3'];
|
|
|
|
const findFn = jest.fn(async (item: string) => {
|
|
await sleep(50);
|
|
return item === '2';
|
|
});
|
|
|
|
const timeBefore = Date.now();
|
|
await expect(findAsyncSequential(items, findFn)).resolves.toEqual('2');
|
|
const timeAfter = Date.now();
|
|
|
|
expect(findFn).toHaveBeenCalledTimes(2);
|
|
expect(findFn).toHaveBeenNthCalledWith(1, '1');
|
|
expect(findFn).toHaveBeenNthCalledWith(2, '2');
|
|
|
|
const timeTotal = timeAfter - timeBefore;
|
|
expect(timeTotal > 100);
|
|
expect(timeTotal < 150);
|
|
});
|
|
});
|
|
|
|
describe('findFolderContainingFile', () => {
|
|
test('find appropriate folder', async () => {
|
|
await expect(
|
|
findFolderContainingFile(
|
|
['/abcdef', '/gehij', __dirname, '/klmn'],
|
|
'index.test.ts',
|
|
),
|
|
).resolves.toEqual(__dirname);
|
|
});
|
|
|
|
test('return undefined if no folder contain such file', async () => {
|
|
await expect(
|
|
findFolderContainingFile(['/abcdef', '/gehij', '/klmn'], 'index.test.ts'),
|
|
).resolves.toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('getFolderContainingFile', () => {
|
|
test('get appropriate folder', async () => {
|
|
await expect(
|
|
getFolderContainingFile(
|
|
['/abcdef', '/gehij', __dirname, '/klmn'],
|
|
'index.test.ts',
|
|
),
|
|
).resolves.toEqual(__dirname);
|
|
});
|
|
|
|
test('throw if no folder contain such file', async () => {
|
|
await expect(
|
|
getFolderContainingFile(['/abcdef', '/gehij', '/klmn'], 'index.test.ts'),
|
|
).rejects.toThrowErrorMatchingSnapshot();
|
|
});
|
|
});
|
|
|
|
describe('updateTranslationFileMessages', () => {
|
|
test('should update messages', () => {
|
|
expect(
|
|
updateTranslationFileMessages(
|
|
{
|
|
path: 'abc',
|
|
content: {
|
|
t1: {message: 't1 message', description: 't1 desc'},
|
|
t2: {message: 't2 message', description: 't2 desc'},
|
|
t3: {message: 't3 message', description: 't3 desc'},
|
|
},
|
|
},
|
|
(message) => `prefix ${message} suffix`,
|
|
),
|
|
).toEqual({
|
|
path: 'abc',
|
|
content: {
|
|
t1: {message: 'prefix t1 message suffix', description: 't1 desc'},
|
|
t2: {message: 'prefix t2 message suffix', description: 't2 desc'},
|
|
t3: {message: 'prefix t3 message suffix', description: 't3 desc'},
|
|
},
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('readDefaultCodeTranslationMessages', () => {
|
|
const dirPath = path.resolve(
|
|
__dirname,
|
|
'__fixtures__',
|
|
'defaultCodeTranslations',
|
|
);
|
|
|
|
async function readAsJSON(filename: string) {
|
|
return JSON.parse(
|
|
await fs.readFile(path.resolve(dirPath, filename), 'utf8'),
|
|
);
|
|
}
|
|
|
|
test('for empty locale', async () => {
|
|
await expect(
|
|
readDefaultCodeTranslationMessages({
|
|
locale: '',
|
|
dirPath,
|
|
}),
|
|
).resolves.toEqual({});
|
|
});
|
|
|
|
test('for unexisting locale', async () => {
|
|
await expect(
|
|
readDefaultCodeTranslationMessages({
|
|
locale: 'es',
|
|
dirPath,
|
|
}),
|
|
).resolves.toEqual({});
|
|
});
|
|
|
|
test('for fr but bad folder', async () => {
|
|
await expect(
|
|
readDefaultCodeTranslationMessages({
|
|
locale: '',
|
|
dirPath: __dirname,
|
|
}),
|
|
).resolves.toEqual({});
|
|
});
|
|
|
|
test('for fr', async () => {
|
|
await expect(
|
|
readDefaultCodeTranslationMessages({
|
|
locale: 'fr',
|
|
dirPath,
|
|
}),
|
|
).resolves.toEqual(await readAsJSON('fr.json'));
|
|
});
|
|
|
|
test('for fr_FR', async () => {
|
|
await expect(
|
|
readDefaultCodeTranslationMessages({
|
|
locale: 'fr_FR',
|
|
dirPath,
|
|
}),
|
|
).resolves.toEqual(await readAsJSON('fr_FR.json'));
|
|
});
|
|
|
|
test('for en', async () => {
|
|
await expect(
|
|
readDefaultCodeTranslationMessages({
|
|
locale: 'en',
|
|
dirPath,
|
|
}),
|
|
).resolves.toEqual(await readAsJSON('en.json'));
|
|
});
|
|
|
|
test('for en_US', async () => {
|
|
await expect(
|
|
readDefaultCodeTranslationMessages({
|
|
locale: 'en_US',
|
|
dirPath,
|
|
}),
|
|
).resolves.toEqual(await readAsJSON('en.json'));
|
|
});
|
|
|
|
test('for en_WHATEVER', async () => {
|
|
await expect(
|
|
readDefaultCodeTranslationMessages({
|
|
locale: 'en_WHATEVER',
|
|
dirPath,
|
|
}),
|
|
).resolves.toEqual(await readAsJSON('en.json'));
|
|
});
|
|
});
|
|
|
|
describe('parseMarkdownHeadingId', () => {
|
|
test('can parse simple heading without id', () => {
|
|
expect(parseMarkdownHeadingId('## Some heading')).toEqual({
|
|
text: '## Some heading',
|
|
id: undefined,
|
|
});
|
|
});
|
|
|
|
test('can parse simple heading with id', () => {
|
|
expect(parseMarkdownHeadingId('## Some heading {#custom-_id}')).toEqual({
|
|
text: '## Some heading',
|
|
id: 'custom-_id',
|
|
});
|
|
});
|
|
|
|
test('can parse heading not ending with the id', () => {
|
|
expect(parseMarkdownHeadingId('## {#custom-_id} Some heading')).toEqual({
|
|
text: '## {#custom-_id} Some heading',
|
|
id: undefined,
|
|
});
|
|
});
|
|
|
|
test('can parse heading with multiple id', () => {
|
|
expect(parseMarkdownHeadingId('## Some heading {#id1} {#id2}')).toEqual({
|
|
text: '## Some heading {#id1}',
|
|
id: 'id2',
|
|
});
|
|
});
|
|
|
|
test('can parse heading with link and id', () => {
|
|
expect(
|
|
parseMarkdownHeadingId(
|
|
'## Some heading [facebook](https://facebook.com) {#id}',
|
|
),
|
|
).toEqual({
|
|
text: '## Some heading [facebook](https://facebook.com)',
|
|
id: 'id',
|
|
});
|
|
});
|
|
|
|
test('can parse heading with only id', () => {
|
|
expect(parseMarkdownHeadingId('## {#id}')).toEqual({
|
|
text: '##',
|
|
id: 'id',
|
|
});
|
|
});
|
|
});
|