@bleedingdev/modern-js-plugin-i18n 3.9.0-ultramodern.4 → 3.9.0-ultramodern.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/cli/index.js +0 -22
- package/dist/cjs/runtime/Link.js +19 -32
- package/dist/cjs/runtime/context.js +26 -61
- package/dist/cjs/runtime/contextHelpers.js +5 -5
- package/dist/cjs/runtime/core.js +17 -14
- package/dist/cjs/runtime/hooks.js +7 -8
- package/dist/cjs/runtime/localizedPaths.js +11 -9
- package/dist/cjs/runtime/providerComposition.js +37 -13
- package/dist/cjs/runtime/routerAdapter.js +35 -140
- package/dist/cjs/runtime/utils.js +17 -19
- package/dist/cjs/server/index.js +10 -7
- package/dist/cjs/server/redirectPolicy.js +36 -31
- package/dist/cjs/shared/urlStrategy.js +18 -0
- package/dist/esm/cli/index.mjs +0 -22
- package/dist/esm/runtime/Link.mjs +19 -32
- package/dist/esm/runtime/context.mjs +18 -62
- package/dist/esm/runtime/contextHelpers.mjs +5 -5
- package/dist/esm/runtime/core.mjs +8 -5
- package/dist/esm/runtime/hooks.mjs +7 -8
- package/dist/esm/runtime/localizedPaths.mjs +11 -9
- package/dist/esm/runtime/providerComposition.mjs +38 -14
- package/dist/esm/runtime/routerAdapter.mjs +30 -141
- package/dist/esm/runtime/utils.mjs +15 -17
- package/dist/esm/server/index.mjs +10 -7
- package/dist/esm/server/redirectPolicy.mjs +29 -24
- package/dist/esm/shared/urlStrategy.mjs +0 -0
- package/dist/esm-node/cli/index.mjs +0 -22
- package/dist/esm-node/runtime/Link.mjs +19 -32
- package/dist/esm-node/runtime/context.mjs +18 -62
- package/dist/esm-node/runtime/contextHelpers.mjs +5 -5
- package/dist/esm-node/runtime/core.mjs +8 -5
- package/dist/esm-node/runtime/hooks.mjs +7 -8
- package/dist/esm-node/runtime/localizedPaths.mjs +11 -9
- package/dist/esm-node/runtime/providerComposition.mjs +38 -14
- package/dist/esm-node/runtime/routerAdapter.mjs +30 -141
- package/dist/esm-node/runtime/utils.mjs +15 -17
- package/dist/esm-node/server/index.mjs +10 -7
- package/dist/esm-node/server/redirectPolicy.mjs +29 -24
- package/dist/esm-node/shared/urlStrategy.mjs +1 -0
- package/dist/types/runtime/context.d.ts +8 -11
- package/dist/types/runtime/contextHelpers.d.ts +2 -2
- package/dist/types/runtime/core.d.ts +11 -2
- package/dist/types/runtime/hooks.d.ts +4 -5
- package/dist/types/runtime/localizedPaths.d.ts +4 -4
- package/dist/types/runtime/providerComposition.d.ts +13 -1
- package/dist/types/runtime/routerAdapter.d.ts +23 -6
- package/dist/types/runtime/utils.d.ts +4 -12
- package/dist/types/server/index.d.ts +2 -0
- package/dist/types/server/redirectPolicy.d.ts +5 -13
- package/dist/types/shared/type.d.ts +0 -14
- package/dist/types/shared/urlStrategy.d.ts +7 -0
- package/package.json +21 -11
- package/rstest.config.mts +2 -8
- package/src/cli/index.ts +1 -43
- package/src/runtime/Link.tsx +20 -40
- package/src/runtime/context.tsx +34 -133
- package/src/runtime/contextHelpers.ts +7 -5
- package/src/runtime/core.tsx +26 -6
- package/src/runtime/hooks.ts +14 -9
- package/src/runtime/localizedPaths.ts +24 -19
- package/src/runtime/providerComposition.tsx +71 -23
- package/src/runtime/routerAdapter.tsx +73 -338
- package/src/runtime/utils.ts +40 -31
- package/src/server/index.ts +44 -9
- package/src/server/redirectPolicy.ts +70 -70
- package/src/shared/type.ts +0 -14
- package/src/shared/urlStrategy.ts +11 -0
- package/tests/federatedI18nBoundaryResolver.test.tsx +71 -0
- package/tests/i18nUtils.test.ts +31 -65
- package/tests/link.test.tsx +13 -511
- package/tests/linkTypes.test.ts +12 -3
- package/tests/localisedUrlRewriteMatrix.fork.test.ts +4 -96
- package/tests/localisedUrls.test.ts +7 -216
- package/tests/reactI18nextRuntimeBoundary.test.ts +1 -82
- package/tests/redirectPolicy.test.ts +39 -30
- package/tests/routerAdapter.test.tsx +95 -1055
- package/tests/federatedI18nBoundary.client.test.tsx +0 -129
- package/tests/federatedI18nBoundary.test.tsx +0 -132
- package/tests/peerCohort.test.ts +0 -25
|
@@ -1,76 +1,77 @@
|
|
|
1
|
-
import {
|
|
2
|
-
isDefaultLocaleRedirectSkipPath,
|
|
3
|
-
localiseTargetPathname,
|
|
4
|
-
matchesPathPrefix,
|
|
5
|
-
shouldSkipLocaleRedirect,
|
|
6
|
-
stripLanguagePrefix,
|
|
7
|
-
} from '@modern-js/i18n-runtime-extensions';
|
|
8
|
-
import type { LocaleDetectionOptions } from '../shared/type';
|
|
1
|
+
import type { I18nUrlStrategy } from '../shared/urlStrategy';
|
|
9
2
|
|
|
10
3
|
interface LocaleRedirectRequest {
|
|
11
4
|
url: string;
|
|
12
|
-
header
|
|
13
|
-
host?: string;
|
|
14
|
-
};
|
|
5
|
+
header?: () => { host?: string };
|
|
15
6
|
}
|
|
16
7
|
|
|
17
8
|
const stripUrlPathPrefix = (pathname: string, urlPath: string): string => {
|
|
18
9
|
const basePath = urlPath.replace('/*', '');
|
|
19
|
-
|
|
20
10
|
if (!basePath || basePath === '/') {
|
|
21
11
|
return pathname;
|
|
22
12
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
? pathname.slice(basePath.length)
|
|
13
|
+
return pathname === basePath || pathname.startsWith(`${basePath}/`)
|
|
14
|
+
? pathname.slice(basePath.length) || '/'
|
|
26
15
|
: pathname;
|
|
27
|
-
|
|
28
|
-
return remainingPath || '/';
|
|
29
16
|
};
|
|
30
17
|
|
|
31
|
-
/**
|
|
32
|
-
* Check if pathname should ignore automatic locale redirect.
|
|
33
|
-
*/
|
|
34
18
|
export const shouldIgnoreRedirect = (
|
|
35
19
|
pathname: string,
|
|
36
20
|
urlPath: string,
|
|
37
21
|
ignoreRedirectRoutes?: string[] | ((pathname: string) => boolean),
|
|
22
|
+
urlStrategy?: I18nUrlStrategy,
|
|
23
|
+
languages: string[] = [],
|
|
38
24
|
): boolean => {
|
|
39
25
|
const remainingPath = stripUrlPathPrefix(pathname, urlPath);
|
|
40
|
-
|
|
41
|
-
|
|
26
|
+
if (urlStrategy?.shouldSkipRedirect?.(remainingPath, languages)) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
if (!ignoreRedirectRoutes) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
const segments = remainingPath.split('/').filter(Boolean);
|
|
33
|
+
if (
|
|
34
|
+
segments[0] &&
|
|
35
|
+
languages.some(
|
|
36
|
+
language => language.toLowerCase() === segments[0].toLowerCase(),
|
|
37
|
+
)
|
|
38
|
+
) {
|
|
39
|
+
segments.shift();
|
|
40
|
+
}
|
|
41
|
+
const normalizedPath = `/${segments.join('/')}`;
|
|
42
|
+
return typeof ignoreRedirectRoutes === 'function'
|
|
43
|
+
? ignoreRedirectRoutes(normalizedPath)
|
|
44
|
+
: ignoreRedirectRoutes.some(
|
|
45
|
+
pattern =>
|
|
46
|
+
normalizedPath === pattern ||
|
|
47
|
+
normalizedPath.startsWith(`${pattern}/`),
|
|
48
|
+
);
|
|
42
49
|
};
|
|
43
50
|
|
|
44
|
-
/**
|
|
45
|
-
* Check if pathname is a static or federation resource request.
|
|
46
|
-
*
|
|
47
|
-
* This includes configured staticRoutePrefixes, the shared default skip policy
|
|
48
|
-
* from ADR-0002, and language-prefixed variants such as /en/static/app.js.
|
|
49
|
-
*/
|
|
51
|
+
/** Native configured/static exclusions remain in effect for every strategy. */
|
|
50
52
|
export const isStaticResourceRequest = (
|
|
51
53
|
pathname: string,
|
|
52
54
|
staticRoutePrefixes: string[],
|
|
53
55
|
languages: string[] = [],
|
|
54
56
|
): boolean => {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const matchesStaticRoutePrefix = (targetPathname: string): boolean =>
|
|
60
|
-
staticRoutePrefixes.some(prefix =>
|
|
61
|
-
matchesPathPrefix(targetPathname, prefix),
|
|
57
|
+
const prefixes = [...staticRoutePrefixes, '/static', '/upload'];
|
|
58
|
+
const matches = (target: string) =>
|
|
59
|
+
prefixes.some(
|
|
60
|
+
prefix => target === prefix || target.startsWith(`${prefix}/`),
|
|
62
61
|
);
|
|
63
|
-
|
|
64
|
-
if (matchesStaticRoutePrefix(pathname)) {
|
|
62
|
+
if (matches(pathname)) {
|
|
65
63
|
return true;
|
|
66
64
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
)
|
|
65
|
+
const segments = pathname.split('/').filter(Boolean);
|
|
66
|
+
if (
|
|
67
|
+
segments[0] &&
|
|
68
|
+
languages.some(
|
|
69
|
+
language => language.toLowerCase() === segments[0].toLowerCase(),
|
|
70
|
+
)
|
|
71
|
+
) {
|
|
72
|
+
return matches(`/${segments.slice(1).join('/')}`);
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
74
75
|
};
|
|
75
76
|
|
|
76
77
|
export const getLanguageFromPath = (
|
|
@@ -78,17 +79,16 @@ export const getLanguageFromPath = (
|
|
|
78
79
|
urlPath: string,
|
|
79
80
|
languages: string[],
|
|
80
81
|
): string | null => {
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return null;
|
|
82
|
+
const host = req.header?.().host;
|
|
83
|
+
const url = new URL(req.url, host ? `http://${host}` : undefined);
|
|
84
|
+
const firstSegment = stripUrlPathPrefix(url.pathname, urlPath)
|
|
85
|
+
.split('/')
|
|
86
|
+
.filter(Boolean)[0];
|
|
87
|
+
return (
|
|
88
|
+
languages.find(
|
|
89
|
+
language => language.toLowerCase() === firstSegment?.toLowerCase(),
|
|
90
|
+
) ?? null
|
|
91
|
+
);
|
|
92
92
|
};
|
|
93
93
|
|
|
94
94
|
export const buildLocalizedUrl = (
|
|
@@ -96,24 +96,24 @@ export const buildLocalizedUrl = (
|
|
|
96
96
|
urlPath: string,
|
|
97
97
|
language: string,
|
|
98
98
|
languages: string[],
|
|
99
|
-
|
|
99
|
+
urlStrategy?: I18nUrlStrategy,
|
|
100
100
|
): string => {
|
|
101
101
|
const url = new URL(req.url);
|
|
102
|
-
const pathname = url.pathname;
|
|
103
102
|
const basePath = urlPath.replace('/*', '');
|
|
104
|
-
const remainingPath = stripUrlPathPrefix(pathname, urlPath);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
103
|
+
const remainingPath = stripUrlPathPrefix(url.pathname, urlPath);
|
|
104
|
+
const segments = remainingPath.split('/').filter(Boolean);
|
|
105
|
+
if (
|
|
106
|
+
segments[0] &&
|
|
107
|
+
languages.some(item => item.toLowerCase() === segments[0].toLowerCase())
|
|
108
|
+
) {
|
|
109
|
+
segments[0] = language;
|
|
110
|
+
} else {
|
|
111
|
+
segments.unshift(language);
|
|
112
|
+
}
|
|
113
|
+
const pathname = urlStrategy
|
|
114
|
+
? urlStrategy.localizePathname(remainingPath, language, languages)
|
|
115
|
+
: `/${segments.join('/')}`;
|
|
116
|
+
return `${basePath === '/' ? '' : basePath}${pathname}${url.search}${url.hash}`;
|
|
117
117
|
};
|
|
118
118
|
|
|
119
119
|
export const createLocaleRedirectResponse = (location: string): Response =>
|
package/src/shared/type.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { LocalisedUrlsOption } from '@modern-js/i18n-runtime-extensions';
|
|
2
1
|
import type {
|
|
3
2
|
LanguageDetectorOptions,
|
|
4
3
|
Resources,
|
|
@@ -11,19 +10,6 @@ export interface BaseLocaleDetectionOptions {
|
|
|
11
10
|
fallbackLanguage?: string;
|
|
12
11
|
detection?: LanguageDetectorOptions;
|
|
13
12
|
ignoreRedirectRoutes?: string[] | ((pathname: string) => boolean);
|
|
14
|
-
/**
|
|
15
|
-
* Enables localised pathnames in addition to the locale prefix.
|
|
16
|
-
*
|
|
17
|
-
* - non-empty object: map canonical route paths to every configured
|
|
18
|
-
* language; route generation then validates that every localisable route
|
|
19
|
-
* path has entries for all configured languages.
|
|
20
|
-
* - absent / `false` / `true` / empty object: keep only locale-prefix
|
|
21
|
-
* behavior (`/en/about`).
|
|
22
|
-
*
|
|
23
|
-
* Strictly opt-in: without a map, `localePathRedirect` + `languages` behave
|
|
24
|
-
* exactly like upstream Modern.js.
|
|
25
|
-
*/
|
|
26
|
-
localisedUrls?: LocalisedUrlsOption;
|
|
27
13
|
}
|
|
28
14
|
|
|
29
15
|
export interface LocaleDetectionOptions extends BaseLocaleDetectionOptions {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Per-plugin URL policy. Search and hash remain owned by native URL handling. */
|
|
2
|
+
export interface I18nUrlStrategy {
|
|
3
|
+
localizePathname(
|
|
4
|
+
pathname: string,
|
|
5
|
+
language: string,
|
|
6
|
+
languages: readonly string[],
|
|
7
|
+
): string;
|
|
8
|
+
canonicalPathname(pathname: string, languages: readonly string[]): string;
|
|
9
|
+
/** Adds exclusions; it cannot enable redirects excluded by native rules. */
|
|
10
|
+
shouldSkipRedirect?(pathname: string, languages: readonly string[]): boolean;
|
|
11
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { RuntimeComponentResolverContext } from '@modern-js/runtime/context';
|
|
2
|
+
import { describe, expect, test } from '@rstest/core';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { renderToStaticMarkup } from 'react-dom/server';
|
|
5
|
+
import {
|
|
6
|
+
FederatedI18nBoundary,
|
|
7
|
+
type FederatedI18nBoundaryProps,
|
|
8
|
+
} from '../src/runtime/context';
|
|
9
|
+
|
|
10
|
+
const props: FederatedI18nBoundaryProps = {
|
|
11
|
+
defaultNamespace: 'inventory',
|
|
12
|
+
resources: {},
|
|
13
|
+
children: <span>child content</span>,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
describe('native federation resolver wrapper', () => {
|
|
17
|
+
test('reports missing integration when no resolver is installed', () => {
|
|
18
|
+
expect(() =>
|
|
19
|
+
renderToStaticMarkup(<FederatedI18nBoundary {...props} />),
|
|
20
|
+
).toThrow(/requires the @modern-js\/i18n-integration runtime plugin/);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('retains the actionable fallback when a resolver does not handle i18n', () => {
|
|
24
|
+
expect(() =>
|
|
25
|
+
renderToStaticMarkup(
|
|
26
|
+
<RuntimeComponentResolverContext.Provider
|
|
27
|
+
value={component => component}
|
|
28
|
+
>
|
|
29
|
+
<FederatedI18nBoundary {...props} />
|
|
30
|
+
</RuntimeComponentResolverContext.Provider>,
|
|
31
|
+
),
|
|
32
|
+
).toThrow(/requires the @modern-js\/i18n-integration runtime plugin/);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('rejects resolution back to the native wrapper', () => {
|
|
36
|
+
expect(() =>
|
|
37
|
+
renderToStaticMarkup(
|
|
38
|
+
<RuntimeComponentResolverContext.Provider
|
|
39
|
+
value={() => FederatedI18nBoundary}
|
|
40
|
+
>
|
|
41
|
+
<FederatedI18nBoundary {...props} />
|
|
42
|
+
</RuntimeComponentResolverContext.Provider>,
|
|
43
|
+
),
|
|
44
|
+
).toThrow(/resolver returned its own wrapper/);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('renders the resolved hook-bearing component with unchanged props', () => {
|
|
48
|
+
let receivedProps: FederatedI18nBoundaryProps | undefined;
|
|
49
|
+
function ResolvedBoundary(value: FederatedI18nBoundaryProps) {
|
|
50
|
+
receivedProps = value;
|
|
51
|
+
const [label] = useState('resolved');
|
|
52
|
+
return (
|
|
53
|
+
<section>
|
|
54
|
+
{label}:{value.children}
|
|
55
|
+
</section>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
const html = renderToStaticMarkup(
|
|
59
|
+
<RuntimeComponentResolverContext.Provider
|
|
60
|
+
value={(component, { name }) =>
|
|
61
|
+
name === 'i18n.FederatedI18nBoundary' ? ResolvedBoundary : component
|
|
62
|
+
}
|
|
63
|
+
>
|
|
64
|
+
<FederatedI18nBoundary {...props} />
|
|
65
|
+
</RuntimeComponentResolverContext.Provider>,
|
|
66
|
+
);
|
|
67
|
+
expect(html).toBe('<section>resolved:<span>child content</span></section>');
|
|
68
|
+
expect(receivedProps?.resources).toBe(props.resources);
|
|
69
|
+
expect(receivedProps?.defaultNamespace).toBe('inventory');
|
|
70
|
+
});
|
|
71
|
+
});
|
package/tests/i18nUtils.test.ts
CHANGED
|
@@ -1,28 +1,11 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
1
4
|
import { describe, expect, test } from '@rstest/core';
|
|
2
|
-
import type { I18nInstance } from '../src/runtime/i18n';
|
|
3
|
-
import {
|
|
4
|
-
DEFAULT_I18NEXT_BACKEND_OPTIONS as NODE_DEFAULT_I18NEXT_BACKEND_OPTIONS,
|
|
5
|
-
resolveDefaultLocalesDir,
|
|
6
|
-
} from '../src/runtime/i18n/backend/defaults.node';
|
|
7
5
|
import {
|
|
8
6
|
FsBackendWithSave,
|
|
9
7
|
resolveFsBackendConstructor,
|
|
10
8
|
} from '../src/runtime/i18n/backend/middleware.node';
|
|
11
|
-
import { initializeI18nInstance } from '../src/runtime/i18n/utils';
|
|
12
|
-
|
|
13
|
-
function createBackendI18nInstance(): I18nInstance {
|
|
14
|
-
return {
|
|
15
|
-
language: 'en',
|
|
16
|
-
isInitialized: false,
|
|
17
|
-
init: async () => undefined,
|
|
18
|
-
use: () => {},
|
|
19
|
-
t: (key: string | string[]) => (Array.isArray(key) ? key[0] : key),
|
|
20
|
-
options: {},
|
|
21
|
-
store: {
|
|
22
|
-
data: {},
|
|
23
|
-
},
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
9
|
|
|
27
10
|
describe('i18n runtime utils', () => {
|
|
28
11
|
test('normalizes node fs backend CJS and ESM namespace shapes', () => {
|
|
@@ -45,50 +28,33 @@ describe('i18n runtime utils', () => {
|
|
|
45
28
|
).toBe(FakeBackend);
|
|
46
29
|
});
|
|
47
30
|
|
|
48
|
-
test('
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
await initializeI18nInstance(
|
|
77
|
-
i18nInstance,
|
|
78
|
-
'cs',
|
|
79
|
-
'en',
|
|
80
|
-
['en', 'cs'],
|
|
81
|
-
{},
|
|
82
|
-
{
|
|
83
|
-
enabled: true,
|
|
84
|
-
loadPath: '/locales/{{lng}}/{{ns}}.json',
|
|
85
|
-
},
|
|
86
|
-
{},
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
expect(init).toHaveBeenCalledTimes(1);
|
|
90
|
-
expect(setTimeoutSpy).not.toHaveBeenCalled();
|
|
91
|
-
|
|
92
|
-
setTimeoutSpy.mockRestore();
|
|
31
|
+
test('loads translation resources through the node fs backend', async () => {
|
|
32
|
+
const root = await mkdtemp(join(tmpdir(), 'i18n-backend-'));
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
await mkdir(join(root, 'cs'), { recursive: true });
|
|
36
|
+
await writeFile(
|
|
37
|
+
join(root, 'cs', 'common.json'),
|
|
38
|
+
JSON.stringify({ title: 'Ahoj' }),
|
|
39
|
+
);
|
|
40
|
+
const backend = new FsBackendWithSave(
|
|
41
|
+
{},
|
|
42
|
+
{ loadPath: join(root, '{{lng}}', '{{ns}}.json') },
|
|
43
|
+
{},
|
|
44
|
+
);
|
|
45
|
+
const resources = await new Promise<unknown>((resolve, reject) => {
|
|
46
|
+
backend.read('cs', 'common', (error: Error | null, data: unknown) => {
|
|
47
|
+
if (error) {
|
|
48
|
+
reject(error);
|
|
49
|
+
} else {
|
|
50
|
+
resolve(data);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(resources).toEqual({ title: 'Ahoj' });
|
|
56
|
+
} finally {
|
|
57
|
+
await rm(root, { force: true, recursive: true });
|
|
58
|
+
}
|
|
93
59
|
});
|
|
94
60
|
});
|