lightnet 3.12.1 → 4.0.0
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/CHANGELOG.md +55 -0
- package/__e2e__/basics-fixture.ts +9 -63
- package/__e2e__/fixtures/basics/astro.config.mjs +7 -12
- package/__e2e__/fixtures/basics/node_modules/.bin/astro +4 -4
- package/__e2e__/fixtures/basics/node_modules/.bin/tailwind +2 -2
- package/__e2e__/fixtures/basics/node_modules/.bin/tailwindcss +2 -2
- package/__e2e__/fixtures/basics/node_modules/.bin/tsc +2 -2
- package/__e2e__/fixtures/basics/node_modules/.bin/tsserver +2 -2
- package/__e2e__/fixtures/basics/package.json +6 -5
- package/__e2e__/fixtures/basics/src/content/categories/christian-living.json +4 -1
- package/__e2e__/fixtures/basics/src/content/categories/teens.json +4 -1
- package/__e2e__/fixtures/basics/src/content/categories/theology.json +4 -1
- package/__e2e__/fixtures/basics/src/content/media/faithful-freestyle--en.json +6 -2
- package/__e2e__/fixtures/basics/src/content/media/how-to-kickflip--de.json +6 -1
- package/__e2e__/fixtures/basics/src/content/media/skate-sounds--en.json +8 -2
- package/__e2e__/fixtures/basics/src/content/media-collections/how-to-articles.json +5 -1
- package/__e2e__/fixtures/basics/src/content/media-types/audio.json +5 -2
- package/__e2e__/fixtures/basics/src/content/media-types/book.json +10 -4
- package/__e2e__/fixtures/basics/src/content/media-types/video.json +5 -2
- package/__e2e__/fixtures/basics/src/pages/[locale]/index.astro +0 -1
- package/__e2e__/fixtures/basics/src/translations/de.yml +0 -8
- package/__e2e__/fixtures/basics/src/translations/en.yml +0 -8
- package/__e2e__/global.teardown.ts +2 -2
- package/__tests__/astro-integration/config.spec.ts +364 -0
- package/__tests__/astro-integration/integration.spec.ts +125 -0
- package/__tests__/astro-integration/tailwind.spec.ts +36 -0
- package/__tests__/content/content-schema.spec.ts +109 -0
- package/__tests__/content/get-media-collections.spec.ts +72 -0
- package/__tests__/content/query-media-items.spec.ts +213 -0
- package/__tests__/i18n/resolve-current-locale.spec.ts +65 -0
- package/__tests__/i18n/translate-map.spec.ts +19 -0
- package/__tests__/i18n/translate.spec.ts +91 -0
- package/__tests__/pages/details-page/create-content-metadata.spec.ts +43 -25
- package/__tests__/pages/details-page/get-translations.spec.ts +56 -0
- package/__tests__/utils/paths.spec.ts +116 -0
- package/__tests__/utils/urls.spec.ts +9 -4
- package/exports/content.ts +7 -2
- package/exports/i18n.ts +0 -1
- package/exports/index.ts +1 -5
- package/exports/utils.ts +0 -1
- package/package.json +16 -12
- package/src/astro-integration/config.ts +60 -49
- package/src/astro-integration/integration.ts +13 -24
- package/src/astro-integration/tailwind.ts +86 -0
- package/src/astro-integration/validators/validate-inline-translations.ts +51 -0
- package/src/astro-integration/validators/validate-languages.ts +39 -0
- package/src/astro-integration/virtual.d.ts +8 -6
- package/src/astro-integration/vite-plugin-lightnet-config.ts +29 -9
- package/src/components/CarouselSection.astro +7 -11
- package/src/components/CategoriesSection.astro +2 -2
- package/src/components/HighlightSection.astro +4 -7
- package/src/components/Icon.tsx +2 -2
- package/src/components/MediaGallerySection.astro +88 -68
- package/src/components/MediaList.astro +9 -7
- package/src/components/SearchInput.astro +7 -4
- package/src/components/Section.astro +7 -5
- package/src/components/VideoPlayer.astro +2 -3
- package/src/content/content-schema.ts +129 -149
- package/src/content/get-categories.ts +52 -28
- package/src/content/get-languages.ts +29 -8
- package/src/content/get-media-collections.ts +43 -0
- package/src/content/get-media-types.ts +41 -7
- package/src/content/query-media-items.ts +23 -13
- package/src/i18n/bcp-47.ts +8 -0
- package/src/i18n/get-locale-paths.ts +1 -3
- package/src/i18n/locals.d.ts +21 -3
- package/src/i18n/locals.ts +18 -11
- package/src/i18n/resolve-current-locale.ts +18 -0
- package/src/i18n/resolve-language.ts +10 -5
- package/src/i18n/translate-map.ts +70 -0
- package/src/i18n/translate.ts +68 -47
- package/src/layouts/Page.astro +5 -3
- package/src/layouts/components/LanguagePicker.astro +22 -17
- package/src/layouts/components/Menu.astro +2 -5
- package/src/layouts/components/MenuItem.astro +1 -1
- package/src/layouts/components/PageNavigation.astro +29 -29
- package/src/layouts/components/PageTitle.astro +23 -7
- package/src/pages/404Route.astro +2 -1
- package/src/pages/RootRoute.astro +6 -1
- package/src/pages/details-page/DefaultDetailsPage.astro +9 -2
- package/src/pages/details-page/DetailsPageRoute.astro +1 -2
- package/src/pages/details-page/components/AudioPanel.astro +7 -3
- package/src/pages/details-page/components/AudioPlayer.astro +2 -2
- package/src/pages/details-page/components/ContentSection.astro +67 -44
- package/src/pages/details-page/components/MediaCollection.astro +8 -4
- package/src/pages/details-page/components/MediaCollectionsSection.astro +3 -6
- package/src/pages/details-page/components/main-details/EditButton.astro +22 -10
- package/src/pages/details-page/components/main-details/OpenButton.astro +17 -12
- package/src/pages/details-page/components/main-details/ShareButton.astro +3 -2
- package/src/pages/details-page/components/more-details/Categories.astro +5 -3
- package/src/pages/details-page/components/more-details/Languages.astro +12 -7
- package/src/pages/details-page/utils/create-content-metadata.ts +24 -9
- package/src/pages/details-page/utils/get-translations.ts +6 -0
- package/src/pages/search-page/components/LoadingSkeleton.tsx +6 -5
- package/src/pages/search-page/components/SearchFilter.astro +10 -21
- package/src/pages/search-page/components/SearchFilter.tsx +2 -2
- package/src/pages/search-page/components/SearchList.astro +10 -7
- package/src/pages/search-page/components/SearchListItem.tsx +5 -4
- package/src/pages/search-page/hooks/use-search.ts +5 -2
- package/src/utils/lazy.ts +20 -0
- package/src/utils/paths.ts +40 -3
- package/src/utils/urls.ts +1 -2
- package/src/utils/verify-schema.ts +12 -10
- package/tailwind.config.ts +1 -25
- package/vitest.config.js +18 -2
- package/src/astro-integration/project-context.ts +0 -5
- package/src/content/compare-media-collection-items.ts +0 -24
- package/src/i18n/resolve-default-locale.ts +0 -19
- package/src/i18n/resolve-locales.ts +0 -5
- package/src/pages/details-page/utils/get-collection-items.ts +0 -29
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { expect, test, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
MediaCollectionEntry,
|
|
5
|
+
MediaItemEntry,
|
|
6
|
+
} from "../../src/content/content-schema"
|
|
7
|
+
import { queryMediaItems } from "../../src/content/query-media-items"
|
|
8
|
+
|
|
9
|
+
vi.mock("astro:content", async () => {
|
|
10
|
+
const { z } = await import("astro/zod")
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
getCollection: vi.fn(),
|
|
14
|
+
defineCollection: (definition: unknown) => definition,
|
|
15
|
+
reference: () => z.object({ id: z.string(), collection: z.string() }),
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const image = {
|
|
20
|
+
src: "/image.webp",
|
|
21
|
+
width: 400,
|
|
22
|
+
height: 600,
|
|
23
|
+
format: "webp",
|
|
24
|
+
} as const
|
|
25
|
+
|
|
26
|
+
const mediaItem = ({
|
|
27
|
+
id,
|
|
28
|
+
title,
|
|
29
|
+
type,
|
|
30
|
+
language,
|
|
31
|
+
dateCreated,
|
|
32
|
+
categories,
|
|
33
|
+
}: {
|
|
34
|
+
id: string
|
|
35
|
+
title: string
|
|
36
|
+
type: string
|
|
37
|
+
language: string
|
|
38
|
+
dateCreated: string
|
|
39
|
+
categories?: string[]
|
|
40
|
+
}): MediaItemEntry => ({
|
|
41
|
+
id,
|
|
42
|
+
data: {
|
|
43
|
+
commonId: id,
|
|
44
|
+
title,
|
|
45
|
+
type: { id: type, collection: "media-types" },
|
|
46
|
+
description: "",
|
|
47
|
+
authors: [],
|
|
48
|
+
dateCreated,
|
|
49
|
+
categories: categories?.map((category) => ({
|
|
50
|
+
id: category,
|
|
51
|
+
collection: "categories",
|
|
52
|
+
})),
|
|
53
|
+
language,
|
|
54
|
+
image,
|
|
55
|
+
content: [{ type: "upload", url: "/files/example.pdf" }],
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const mediaItems: MediaItemEntry[] = [
|
|
60
|
+
mediaItem({
|
|
61
|
+
id: "a",
|
|
62
|
+
title: "Alpha",
|
|
63
|
+
type: "book",
|
|
64
|
+
language: "en",
|
|
65
|
+
dateCreated: "2024-01-02",
|
|
66
|
+
categories: ["family"],
|
|
67
|
+
}),
|
|
68
|
+
mediaItem({
|
|
69
|
+
id: "b",
|
|
70
|
+
title: "Beta",
|
|
71
|
+
type: "book",
|
|
72
|
+
language: "en",
|
|
73
|
+
dateCreated: "2025-01-03",
|
|
74
|
+
categories: ["theology"],
|
|
75
|
+
}),
|
|
76
|
+
mediaItem({
|
|
77
|
+
id: "c",
|
|
78
|
+
title: "Charlie",
|
|
79
|
+
type: "video",
|
|
80
|
+
language: "de",
|
|
81
|
+
dateCreated: "2023-03-04",
|
|
82
|
+
categories: ["theology"],
|
|
83
|
+
}),
|
|
84
|
+
mediaItem({
|
|
85
|
+
id: "d",
|
|
86
|
+
title: "Delta",
|
|
87
|
+
type: "book",
|
|
88
|
+
language: "en",
|
|
89
|
+
dateCreated: "2022-01-01",
|
|
90
|
+
}),
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
const mediaCollections: MediaCollectionEntry[] = [
|
|
94
|
+
{
|
|
95
|
+
id: "featured",
|
|
96
|
+
data: {
|
|
97
|
+
label: { en: "Featured" },
|
|
98
|
+
mediaItems: [
|
|
99
|
+
{ id: "d", collection: "media" },
|
|
100
|
+
{ id: "b", collection: "media" },
|
|
101
|
+
{ id: "a", collection: "media" },
|
|
102
|
+
{ id: "a", collection: "media" },
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: "german",
|
|
108
|
+
data: {
|
|
109
|
+
label: { en: "German" },
|
|
110
|
+
mediaItems: [{ id: "c", collection: "media" }],
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
test("Should filter by media type", async () => {
|
|
116
|
+
const result = await queryMediaItems(
|
|
117
|
+
Promise.resolve(mediaItems),
|
|
118
|
+
Promise.resolve(mediaCollections),
|
|
119
|
+
{
|
|
120
|
+
where: { type: "book" },
|
|
121
|
+
},
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
expect(result.map((item) => item.id)).toEqual(["a", "b", "d"])
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test("Should combine collection and other filters using AND", async () => {
|
|
128
|
+
const result = await queryMediaItems(
|
|
129
|
+
Promise.resolve(mediaItems),
|
|
130
|
+
Promise.resolve(mediaCollections),
|
|
131
|
+
{
|
|
132
|
+
where: {
|
|
133
|
+
collection: "featured",
|
|
134
|
+
language: "en",
|
|
135
|
+
type: "book",
|
|
136
|
+
category: "theology",
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
expect(result.map((item) => item.id)).toEqual(["b"])
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
test("Should keep collection order when no orderBy is set", async () => {
|
|
145
|
+
const result = await queryMediaItems(
|
|
146
|
+
Promise.resolve(mediaItems),
|
|
147
|
+
Promise.resolve(mediaCollections),
|
|
148
|
+
{
|
|
149
|
+
where: {
|
|
150
|
+
collection: "featured",
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
expect(result.map((item) => item.id)).toEqual(["d", "b", "a"])
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test("Should override collection order when orderBy is set", async () => {
|
|
159
|
+
const result = await queryMediaItems(
|
|
160
|
+
Promise.resolve(mediaItems),
|
|
161
|
+
Promise.resolve(mediaCollections),
|
|
162
|
+
{
|
|
163
|
+
where: {
|
|
164
|
+
collection: "featured",
|
|
165
|
+
},
|
|
166
|
+
orderBy: "title",
|
|
167
|
+
},
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
expect(result.map((item) => item.id)).toEqual(["a", "b", "d"])
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
test("Should apply limit after ordering", async () => {
|
|
174
|
+
const result = await queryMediaItems(
|
|
175
|
+
Promise.resolve(mediaItems),
|
|
176
|
+
Promise.resolve(mediaCollections),
|
|
177
|
+
{
|
|
178
|
+
where: {
|
|
179
|
+
collection: "featured",
|
|
180
|
+
},
|
|
181
|
+
limit: 2,
|
|
182
|
+
},
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
expect(result.map((item) => item.id)).toEqual(["d", "b"])
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
test("Should return an empty list for unknown collection filters", async () => {
|
|
189
|
+
const result = await queryMediaItems(
|
|
190
|
+
Promise.resolve(mediaItems),
|
|
191
|
+
Promise.resolve(mediaCollections),
|
|
192
|
+
{
|
|
193
|
+
where: {
|
|
194
|
+
collection: "does-not-exist",
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
expect(result).toEqual([])
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
test("Should sort by dateCreated descending", async () => {
|
|
203
|
+
const result = await queryMediaItems(
|
|
204
|
+
Promise.resolve(mediaItems),
|
|
205
|
+
Promise.resolve(mediaCollections),
|
|
206
|
+
{
|
|
207
|
+
where: { type: "book" },
|
|
208
|
+
orderBy: "dateCreated",
|
|
209
|
+
},
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
expect(result.map((item) => item.id)).toEqual(["b", "a", "d"])
|
|
213
|
+
})
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { resolveCurrentLocaleFromPathname } from "../../src/i18n/resolve-current-locale"
|
|
4
|
+
|
|
5
|
+
test("Should resolve current locale from localized path", () => {
|
|
6
|
+
const currentLocale = resolveCurrentLocaleFromPathname({
|
|
7
|
+
pathname: "/de/media",
|
|
8
|
+
locales: ["en", "de"],
|
|
9
|
+
defaultLocale: "en",
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
expect(currentLocale).toBe("de")
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test("Should fallback to default locale for unlocalized path", () => {
|
|
16
|
+
const currentLocale = resolveCurrentLocaleFromPathname({
|
|
17
|
+
pathname: "/admin",
|
|
18
|
+
locales: ["en", "de"],
|
|
19
|
+
defaultLocale: "en",
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
expect(currentLocale).toBe("en")
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test("Should resolve current locale from localized path under Astro base", () => {
|
|
26
|
+
const currentLocale = resolveCurrentLocaleFromPathname({
|
|
27
|
+
pathname: "/docs/de/media",
|
|
28
|
+
base: "/docs",
|
|
29
|
+
locales: ["en", "de"],
|
|
30
|
+
defaultLocale: "en",
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
expect(currentLocale).toBe("de")
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test("Should fallback to default locale for root path", () => {
|
|
37
|
+
const currentLocale = resolveCurrentLocaleFromPathname({
|
|
38
|
+
pathname: "/",
|
|
39
|
+
locales: ["en", "de"],
|
|
40
|
+
defaultLocale: "en",
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
expect(currentLocale).toBe("en")
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test("Should fallback to default locale for base root path", () => {
|
|
47
|
+
const currentLocale = resolveCurrentLocaleFromPathname({
|
|
48
|
+
pathname: "/docs",
|
|
49
|
+
base: "/docs",
|
|
50
|
+
locales: ["en", "de"],
|
|
51
|
+
defaultLocale: "en",
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
expect(currentLocale).toBe("en")
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test("Should fallback to default locale for unsupported locale-like segment", () => {
|
|
58
|
+
const currentLocale = resolveCurrentLocaleFromPathname({
|
|
59
|
+
pathname: "/de-AT/media",
|
|
60
|
+
locales: ["en", "de"],
|
|
61
|
+
defaultLocale: "en",
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
expect(currentLocale).toBe("en")
|
|
65
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { useTranslateMap } from "../../src/i18n/translate-map"
|
|
4
|
+
|
|
5
|
+
const tMap = useTranslateMap("de")
|
|
6
|
+
|
|
7
|
+
test("Should resolve the localized value for the current locale", () => {
|
|
8
|
+
expect(tMap({ en: "Hello", de: "Hallo" }, { path: ["title"] })).toBe("Hallo")
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
test("Should fallback to default locale when current locale is missing", () => {
|
|
12
|
+
expect(tMap({ en: "Hello" }, { path: ["title"] })).toBe("Hello")
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test("Should include the context path in missing translation errors", () => {
|
|
16
|
+
expect(() =>
|
|
17
|
+
tMap({ fr: "Bonjour" }, { path: ["content", 0, "label"] }),
|
|
18
|
+
).toThrow('Missing translation map value for "content.0.label"')
|
|
19
|
+
})
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { expect, test, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
const importTranslate = async () => import("../../src/i18n/translate")
|
|
4
|
+
const importTranslations = async () => import("../../src/i18n/translations")
|
|
5
|
+
|
|
6
|
+
test("Should load built-in translations for non-default site locales", async () => {
|
|
7
|
+
const { useTranslate } = await importTranslate()
|
|
8
|
+
const t = await useTranslate("de")
|
|
9
|
+
expect(t("ln.search.title")).toBe("Suche")
|
|
10
|
+
expect(t("ln.header.select-language")).toBe("Sprache auswählen")
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
test("Should fail on missing translation key", async () => {
|
|
14
|
+
const { useTranslate } = await importTranslate()
|
|
15
|
+
const t = await useTranslate("de")
|
|
16
|
+
expect(() => t("x.missing-translation")).toThrow(
|
|
17
|
+
"Missing translation: 'x.missing-translation' is undefined for language 'de'.",
|
|
18
|
+
)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test("Should not load user translations for missing locale file", async () => {
|
|
22
|
+
const { loadTranslations } = await importTranslations()
|
|
23
|
+
const translations = await loadTranslations("en")
|
|
24
|
+
expect(translations["home.all-items"]).toBeUndefined()
|
|
25
|
+
expect(translations["ln.search.title"]).toBe("Search")
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test("Should fallback to configured fallback languages for missing keys", async () => {
|
|
29
|
+
vi.resetModules()
|
|
30
|
+
vi.doMock("../../src/i18n/translations", async () => {
|
|
31
|
+
const actual = await vi.importActual<
|
|
32
|
+
typeof import("../../src/i18n/translations")
|
|
33
|
+
>("../../src/i18n/translations")
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
...actual,
|
|
37
|
+
loadTranslations: vi.fn(async (bcp47: string) => {
|
|
38
|
+
if (bcp47 === "de") {
|
|
39
|
+
return {}
|
|
40
|
+
}
|
|
41
|
+
if (bcp47 === "en") {
|
|
42
|
+
return {
|
|
43
|
+
"x.fallback-only": "Fallback value",
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return {}
|
|
47
|
+
}),
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const { useTranslate } = await importTranslate()
|
|
52
|
+
const t = await useTranslate("de")
|
|
53
|
+
expect(t("x.fallback-only")).toBe("Fallback value")
|
|
54
|
+
|
|
55
|
+
vi.doUnmock("../../src/i18n/translations")
|
|
56
|
+
vi.resetModules()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test("Should allow translations whose value matches the key", async () => {
|
|
60
|
+
vi.resetModules()
|
|
61
|
+
vi.doMock("../../src/i18n/translations", async () => {
|
|
62
|
+
const actual = await vi.importActual<
|
|
63
|
+
typeof import("../../src/i18n/translations")
|
|
64
|
+
>("../../src/i18n/translations")
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
...actual,
|
|
68
|
+
loadTranslations: vi.fn(async (bcp47: string) => {
|
|
69
|
+
if (bcp47 === "de") {
|
|
70
|
+
return {
|
|
71
|
+
"ln.search.title": "Suche",
|
|
72
|
+
"x.same-as-value": "x.same-as-value",
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (bcp47 === "en") {
|
|
76
|
+
return {
|
|
77
|
+
"ln.search.title": "Search",
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return {}
|
|
81
|
+
}),
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
const { useTranslate } = await importTranslate()
|
|
86
|
+
const t = await useTranslate("de")
|
|
87
|
+
expect(t("x.same-as-value")).toBe("x.same-as-value")
|
|
88
|
+
|
|
89
|
+
vi.doUnmock("../../src/i18n/translations")
|
|
90
|
+
vi.resetModules()
|
|
91
|
+
})
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import { expect, test } from "vitest"
|
|
2
2
|
|
|
3
|
+
import { useTranslateMap } from "../../../src/i18n/translate-map"
|
|
3
4
|
import { createContentMetadata } from "../../../src/pages/details-page/utils/create-content-metadata"
|
|
4
5
|
|
|
6
|
+
const tMap = useTranslateMap("en")
|
|
7
|
+
const tMapDe = useTranslateMap("de")
|
|
8
|
+
|
|
5
9
|
test("Should create complete content metadata", () => {
|
|
6
|
-
expect(
|
|
10
|
+
expect(
|
|
11
|
+
createContentMetadata({ url: "https://some.host/some.pDf" }, tMap, {
|
|
12
|
+
path: ["content", 0],
|
|
13
|
+
}),
|
|
14
|
+
).toEqual({
|
|
7
15
|
url: "https://some.host/some.pDf",
|
|
8
16
|
canBeOpened: true,
|
|
9
17
|
type: "text",
|
|
10
18
|
target: "_blank",
|
|
11
|
-
|
|
19
|
+
labelText: "some",
|
|
12
20
|
isExternal: true,
|
|
13
21
|
extension: "pdf",
|
|
14
22
|
})
|
|
@@ -19,7 +27,7 @@ test("Should create complete content metadata", () => {
|
|
|
19
27
|
expected: {
|
|
20
28
|
canBeOpened: true,
|
|
21
29
|
target: "_blank",
|
|
22
|
-
|
|
30
|
+
labelText: "youtube.com",
|
|
23
31
|
isExternal: true,
|
|
24
32
|
extension: "",
|
|
25
33
|
type: "link",
|
|
@@ -30,7 +38,7 @@ test("Should create complete content metadata", () => {
|
|
|
30
38
|
expected: {
|
|
31
39
|
canBeOpened: true,
|
|
32
40
|
target: "_blank",
|
|
33
|
-
|
|
41
|
+
labelText: "wikipedia.org",
|
|
34
42
|
isExternal: true,
|
|
35
43
|
extension: "",
|
|
36
44
|
type: "link",
|
|
@@ -42,7 +50,7 @@ test("Should create complete content metadata", () => {
|
|
|
42
50
|
canBeOpened: true,
|
|
43
51
|
type: "text",
|
|
44
52
|
target: "_blank",
|
|
45
|
-
|
|
53
|
+
labelText: "some",
|
|
46
54
|
isExternal: true,
|
|
47
55
|
extension: "pdf",
|
|
48
56
|
},
|
|
@@ -53,7 +61,7 @@ test("Should create complete content metadata", () => {
|
|
|
53
61
|
type: "link",
|
|
54
62
|
canBeOpened: false,
|
|
55
63
|
target: "_blank",
|
|
56
|
-
|
|
64
|
+
labelText: "some",
|
|
57
65
|
isExternal: true,
|
|
58
66
|
extension: "unknown",
|
|
59
67
|
},
|
|
@@ -64,7 +72,7 @@ test("Should create complete content metadata", () => {
|
|
|
64
72
|
canBeOpened: true,
|
|
65
73
|
type: "text",
|
|
66
74
|
target: "_self",
|
|
67
|
-
|
|
75
|
+
labelText: "my",
|
|
68
76
|
isExternal: false,
|
|
69
77
|
extension: "pdf",
|
|
70
78
|
},
|
|
@@ -74,7 +82,7 @@ test("Should create complete content metadata", () => {
|
|
|
74
82
|
expected: {
|
|
75
83
|
canBeOpened: true,
|
|
76
84
|
target: "_self",
|
|
77
|
-
|
|
85
|
+
labelText: "my-id",
|
|
78
86
|
isExternal: false,
|
|
79
87
|
extension: "",
|
|
80
88
|
type: "link",
|
|
@@ -85,7 +93,7 @@ test("Should create complete content metadata", () => {
|
|
|
85
93
|
expected: {
|
|
86
94
|
canBeOpened: false,
|
|
87
95
|
target: "_self",
|
|
88
|
-
|
|
96
|
+
labelText: "my",
|
|
89
97
|
isExternal: false,
|
|
90
98
|
type: "link",
|
|
91
99
|
extension: "unknown",
|
|
@@ -96,7 +104,7 @@ test("Should create complete content metadata", () => {
|
|
|
96
104
|
expected: {
|
|
97
105
|
canBeOpened: false,
|
|
98
106
|
target: "_self",
|
|
99
|
-
|
|
107
|
+
labelText: "some",
|
|
100
108
|
isExternal: false,
|
|
101
109
|
extension: "zip",
|
|
102
110
|
type: "package",
|
|
@@ -104,19 +112,9 @@ test("Should create complete content metadata", () => {
|
|
|
104
112
|
},
|
|
105
113
|
{
|
|
106
114
|
url: "/some.zip",
|
|
107
|
-
label: "foo",
|
|
115
|
+
label: { en: "foo" },
|
|
108
116
|
expected: {
|
|
109
|
-
|
|
110
|
-
isExternal: false,
|
|
111
|
-
extension: "zip",
|
|
112
|
-
type: "package",
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
{
|
|
116
|
-
url: "/some.zip",
|
|
117
|
-
label: "",
|
|
118
|
-
expected: {
|
|
119
|
-
label: "some",
|
|
117
|
+
labelText: "foo",
|
|
120
118
|
isExternal: false,
|
|
121
119
|
extension: "zip",
|
|
122
120
|
type: "package",
|
|
@@ -124,12 +122,32 @@ test("Should create complete content metadata", () => {
|
|
|
124
122
|
},
|
|
125
123
|
].forEach(({ url, expected, label }) => {
|
|
126
124
|
test(`Should create content metadata for url '${url}' ${label !== undefined ? `and label '${label}'` : ""}`, () => {
|
|
127
|
-
expect(
|
|
125
|
+
expect(
|
|
126
|
+
createContentMetadata({ url, label }, tMap, {
|
|
127
|
+
path: ["content", 0],
|
|
128
|
+
}),
|
|
129
|
+
).toMatchObject(expected)
|
|
128
130
|
})
|
|
129
131
|
})
|
|
130
132
|
|
|
131
133
|
test("Should override name with input", () => {
|
|
132
134
|
expect(
|
|
133
|
-
createContentMetadata(
|
|
134
|
-
|
|
135
|
+
createContentMetadata(
|
|
136
|
+
{ url: "/path/to/a.file", label: { en: "My file" } },
|
|
137
|
+
tMap,
|
|
138
|
+
{ path: ["content", 0] },
|
|
139
|
+
),
|
|
140
|
+
).toMatchObject({ labelText: "My file" })
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
test("Should resolve localized content labels by locale", () => {
|
|
144
|
+
const result = createContentMetadata(
|
|
145
|
+
{
|
|
146
|
+
url: "/files/book.pdf",
|
|
147
|
+
label: { en: "Read", de: "Lesen" },
|
|
148
|
+
},
|
|
149
|
+
tMapDe,
|
|
150
|
+
{ path: ["content", 0] },
|
|
151
|
+
)
|
|
152
|
+
expect(result.labelText).toBe("Lesen")
|
|
135
153
|
})
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { beforeEach, expect, test, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
const mocks = vi.hoisted(() => ({
|
|
4
|
+
getMediaItems: vi.fn(),
|
|
5
|
+
getMediaItem: vi.fn(),
|
|
6
|
+
}))
|
|
7
|
+
|
|
8
|
+
vi.mock("../../../src/content/get-media-items", () => ({
|
|
9
|
+
getMediaItems: mocks.getMediaItems,
|
|
10
|
+
getMediaItem: mocks.getMediaItem,
|
|
11
|
+
}))
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
vi.resetModules()
|
|
15
|
+
vi.clearAllMocks()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test("Should return empty translations when commonId is missing", async () => {
|
|
19
|
+
mocks.getMediaItems.mockResolvedValue([
|
|
20
|
+
{ id: "book-en", data: { commonId: "book", language: "en" } },
|
|
21
|
+
{ id: "book-de", data: { commonId: "book", language: "de" } },
|
|
22
|
+
])
|
|
23
|
+
mocks.getMediaItem.mockResolvedValue({
|
|
24
|
+
id: "standalone-en",
|
|
25
|
+
data: { language: "en" },
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const { getTranslations } =
|
|
29
|
+
await import("../../../src/pages/details-page/utils/get-translations")
|
|
30
|
+
const result = await getTranslations("standalone-en")
|
|
31
|
+
|
|
32
|
+
expect(result).toEqual([])
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test("Should return sorted translations and ignore unique or missing commonId", async () => {
|
|
36
|
+
mocks.getMediaItems.mockResolvedValue([
|
|
37
|
+
{ id: "book-de", data: { commonId: "book", language: "de" } },
|
|
38
|
+
{ id: "book-en", data: { commonId: "book", language: "en" } },
|
|
39
|
+
{ id: "book-es", data: { commonId: "book", language: "es" } },
|
|
40
|
+
{ id: "guide-en", data: { commonId: "guide", language: "en" } },
|
|
41
|
+
{ id: "standalone-fr", data: { language: "fr" } },
|
|
42
|
+
])
|
|
43
|
+
mocks.getMediaItem.mockResolvedValue({
|
|
44
|
+
id: "book-en",
|
|
45
|
+
data: { commonId: "book", language: "en" },
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const { getTranslations } =
|
|
49
|
+
await import("../../../src/pages/details-page/utils/get-translations")
|
|
50
|
+
const result = await getTranslations("book-en")
|
|
51
|
+
|
|
52
|
+
expect(result).toEqual([
|
|
53
|
+
{ id: "book-de", language: "de" },
|
|
54
|
+
{ id: "book-es", language: "es" },
|
|
55
|
+
])
|
|
56
|
+
})
|