lightnet 4.0.0 → 4.0.1

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.
Files changed (50) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/package.json +11 -1
  3. package/__e2e__/admin.spec.ts +0 -20
  4. package/__e2e__/basics-fixture.ts +0 -23
  5. package/__e2e__/fixtures/basics/astro.config.mjs +0 -35
  6. package/__e2e__/fixtures/basics/node_modules/.bin/astro +0 -21
  7. package/__e2e__/fixtures/basics/node_modules/.bin/tailwind +0 -21
  8. package/__e2e__/fixtures/basics/node_modules/.bin/tailwindcss +0 -21
  9. package/__e2e__/fixtures/basics/node_modules/.bin/tsc +0 -21
  10. package/__e2e__/fixtures/basics/node_modules/.bin/tsserver +0 -21
  11. package/__e2e__/fixtures/basics/package.json +0 -22
  12. package/__e2e__/fixtures/basics/public/favicon.svg +0 -1
  13. package/__e2e__/fixtures/basics/public/files/example.pdf +0 -0
  14. package/__e2e__/fixtures/basics/src/assets/logo.png +0 -0
  15. package/__e2e__/fixtures/basics/src/content/categories/christian-living.json +0 -6
  16. package/__e2e__/fixtures/basics/src/content/categories/teens.json +0 -6
  17. package/__e2e__/fixtures/basics/src/content/categories/theology.json +0 -6
  18. package/__e2e__/fixtures/basics/src/content/media/faithful-freestyle--en.json +0 -17
  19. package/__e2e__/fixtures/basics/src/content/media/how-to-kickflip--de.json +0 -17
  20. package/__e2e__/fixtures/basics/src/content/media/images/cover.jpg +0 -0
  21. package/__e2e__/fixtures/basics/src/content/media/images/how-to-kickflip--en.webp +0 -0
  22. package/__e2e__/fixtures/basics/src/content/media/skate-sounds--en.json +0 -21
  23. package/__e2e__/fixtures/basics/src/content/media-collections/how-to-articles.json +0 -7
  24. package/__e2e__/fixtures/basics/src/content/media-types/audio.json +0 -10
  25. package/__e2e__/fixtures/basics/src/content/media-types/book.json +0 -15
  26. package/__e2e__/fixtures/basics/src/content/media-types/video.json +0 -10
  27. package/__e2e__/fixtures/basics/src/content.config.ts +0 -3
  28. package/__e2e__/fixtures/basics/src/pages/[locale]/index.astro +0 -15
  29. package/__e2e__/fixtures/basics/src/translations/de.yml +0 -1
  30. package/__e2e__/fixtures/basics/src/translations/en.yml +0 -1
  31. package/__e2e__/fixtures/basics/tailwind.config.mjs +0 -8
  32. package/__e2e__/global.teardown.ts +0 -5
  33. package/__e2e__/homepage.spec.ts +0 -123
  34. package/__e2e__/search.spec.ts +0 -14
  35. package/__tests__/astro-integration/config.spec.ts +0 -364
  36. package/__tests__/astro-integration/integration.spec.ts +0 -125
  37. package/__tests__/astro-integration/tailwind.spec.ts +0 -36
  38. package/__tests__/content/content-schema.spec.ts +0 -109
  39. package/__tests__/content/get-media-collections.spec.ts +0 -72
  40. package/__tests__/content/query-media-items.spec.ts +0 -213
  41. package/__tests__/i18n/resolve-current-locale.spec.ts +0 -65
  42. package/__tests__/i18n/translate-map.spec.ts +0 -19
  43. package/__tests__/i18n/translate.spec.ts +0 -91
  44. package/__tests__/pages/details-page/create-content-metadata.spec.ts +0 -153
  45. package/__tests__/pages/details-page/get-translations.spec.ts +0 -56
  46. package/__tests__/utils/markdown.spec.ts +0 -74
  47. package/__tests__/utils/paths.spec.ts +0 -116
  48. package/__tests__/utils/urls.spec.ts +0 -32
  49. package/playwright.config.ts +0 -31
  50. package/vitest.config.js +0 -36
@@ -1,364 +0,0 @@
1
- import { expect, test } from "vitest"
2
-
3
- import {
4
- configSchema,
5
- extendedConfigSchema,
6
- } from "../../src/astro-integration/config"
7
-
8
- const requiredConfig = {
9
- title: { en: "LightNet" },
10
- languages: [
11
- {
12
- code: "en",
13
- label: { en: "English" },
14
- isDefaultSiteLanguage: true,
15
- },
16
- ],
17
- }
18
-
19
- test("Should default credits to false when omitted", () => {
20
- const config = configSchema.parse(requiredConfig)
21
-
22
- expect(config.credits).toBe(false)
23
- })
24
-
25
- test("Should preserve explicit credits false", () => {
26
- const config = configSchema.parse({
27
- ...requiredConfig,
28
- credits: false,
29
- })
30
-
31
- expect(config.credits).toBe(false)
32
- })
33
-
34
- test("Should reject invalid BCP-47 language code", () => {
35
- expect(() =>
36
- configSchema.parse({
37
- ...requiredConfig,
38
- languages: [
39
- {
40
- ...requiredConfig.languages[0],
41
- code: "en_US",
42
- },
43
- ],
44
- }),
45
- ).toThrow(/Invalid BCP-47 language code/)
46
- })
47
-
48
- test("Should reject invalid BCP-47 fallback language code", () => {
49
- expect(() =>
50
- configSchema.parse({
51
- ...requiredConfig,
52
- languages: [
53
- requiredConfig.languages[0],
54
- {
55
- code: "de",
56
- label: { en: "German" },
57
- isSiteLanguage: true,
58
- fallbackLanguages: ["es_MX"],
59
- },
60
- ],
61
- }),
62
- ).toThrow(/Invalid BCP-47 language code/)
63
- })
64
-
65
- test("Should reject duplicate language codes", () => {
66
- expect(() =>
67
- configSchema.parse({
68
- ...requiredConfig,
69
- languages: [
70
- requiredConfig.languages[0],
71
- {
72
- ...requiredConfig.languages[0],
73
- isDefaultSiteLanguage: false,
74
- isSiteLanguage: true,
75
- },
76
- ],
77
- }),
78
- ).toThrow(/Duplicate language code/)
79
- })
80
-
81
- test("Should reject object-based language config", () => {
82
- expect(() =>
83
- configSchema.parse({
84
- ...requiredConfig,
85
- languages: {
86
- en: {
87
- label: { en: "English" },
88
- isDefaultSiteLanguage: true,
89
- },
90
- },
91
- }),
92
- ).toThrow(/expected array, received object/i)
93
- })
94
-
95
- test("Should reject languages without a default", () => {
96
- const parsed = extendedConfigSchema.safeParse({
97
- ...requiredConfig,
98
- languages: [
99
- {
100
- code: "en",
101
- label: { en: "English" },
102
- isSiteLanguage: true,
103
- },
104
- {
105
- code: "de",
106
- label: { en: "German" },
107
- isSiteLanguage: true,
108
- },
109
- ],
110
- })
111
-
112
- expect(parsed.success).toBe(false)
113
- if (parsed.success) {
114
- return
115
- }
116
-
117
- expect(parsed.error.issues).toEqual(
118
- expect.arrayContaining([
119
- expect.objectContaining({
120
- message: "Exactly one language must define isDefaultSiteLanguage: true",
121
- }),
122
- ]),
123
- )
124
- })
125
-
126
- test("Should reject multiple default site languages", () => {
127
- const parsed = extendedConfigSchema.safeParse({
128
- ...requiredConfig,
129
- languages: [
130
- requiredConfig.languages[0],
131
- {
132
- code: "de",
133
- label: { en: "German" },
134
- isDefaultSiteLanguage: true,
135
- },
136
- ],
137
- })
138
-
139
- expect(parsed.success).toBe(false)
140
- if (parsed.success) {
141
- return
142
- }
143
-
144
- expect(parsed.error.issues).toEqual(
145
- expect.arrayContaining([
146
- expect.objectContaining({
147
- message: "Exactly one language must define isDefaultSiteLanguage: true",
148
- }),
149
- ]),
150
- )
151
- })
152
-
153
- test("Should set isSiteLanguage when language is default", () => {
154
- const parsed = extendedConfigSchema.parse(requiredConfig)
155
-
156
- expect(parsed.languages[0].isSiteLanguage).toBe(true)
157
- expect(parsed.locales).toEqual(["en"])
158
- })
159
-
160
- test("Should allow fallback values outside configured site languages", () => {
161
- const parsed = extendedConfigSchema.safeParse({
162
- ...requiredConfig,
163
- languages: [
164
- requiredConfig.languages[0],
165
- {
166
- code: "de",
167
- label: { en: "German" },
168
- isSiteLanguage: true,
169
- fallbackLanguages: ["fr"],
170
- },
171
- ],
172
- })
173
-
174
- expect(parsed.success).toBe(true)
175
- })
176
-
177
- test("Should derive locales and defaultLocale", () => {
178
- const parsed = extendedConfigSchema.safeParse({
179
- ...requiredConfig,
180
- title: {
181
- de: "LightNet",
182
- },
183
- languages: [
184
- {
185
- code: "en",
186
- label: { de: "Englisch" },
187
- },
188
- {
189
- code: "de",
190
- label: { de: "Deutsch" },
191
- isDefaultSiteLanguage: true,
192
- fallbackLanguages: ["es"],
193
- },
194
- {
195
- code: "fr",
196
- label: { de: "Franzosisch" },
197
- },
198
- ],
199
- })
200
-
201
- expect(parsed.success).toBe(true)
202
- if (!parsed.success) {
203
- return
204
- }
205
-
206
- expect(parsed.data.locales).toEqual(["de"])
207
- expect(parsed.data.defaultLocale).toBe("de")
208
- })
209
-
210
- test("Should accept missing non-default locale in title inline translation", () => {
211
- const parsed = extendedConfigSchema.safeParse({
212
- ...requiredConfig,
213
- languages: [
214
- requiredConfig.languages[0],
215
- {
216
- code: "de",
217
- label: { en: "German" },
218
- isSiteLanguage: true,
219
- },
220
- ],
221
- title: {
222
- en: "LightNet",
223
- },
224
- })
225
-
226
- expect(parsed.success).toBe(true)
227
- })
228
-
229
- test("Should reject missing default locale in nested inline translation", () => {
230
- const parsed = extendedConfigSchema.safeParse({
231
- ...requiredConfig,
232
- languages: [
233
- requiredConfig.languages[0],
234
- {
235
- code: "de",
236
- label: { en: "German" },
237
- isSiteLanguage: true,
238
- },
239
- ],
240
- title: {
241
- en: "LightNet",
242
- de: "LichtNet",
243
- },
244
- mainMenu: [
245
- {
246
- href: "/about",
247
- label: {
248
- de: "Uber",
249
- },
250
- requiresLocale: true,
251
- },
252
- ],
253
- })
254
-
255
- expect(parsed.success).toBe(false)
256
- if (parsed.success) {
257
- return
258
- }
259
-
260
- expect(parsed.error.issues).toEqual(
261
- expect.arrayContaining([
262
- expect.objectContaining({
263
- message: 'Missing translation for default locale "en"',
264
- path: ["mainMenu", 0, "label", "en"],
265
- }),
266
- ]),
267
- )
268
- })
269
-
270
- test("Should reject unsupported locale in inline translation", () => {
271
- const parsed = extendedConfigSchema.safeParse({
272
- ...requiredConfig,
273
- languages: [
274
- requiredConfig.languages[0],
275
- {
276
- code: "de",
277
- label: { en: "German" },
278
- isSiteLanguage: true,
279
- },
280
- ],
281
- title: {
282
- en: "LightNet",
283
- fr: "LumiereNet",
284
- },
285
- })
286
-
287
- expect(parsed.success).toBe(false)
288
- if (parsed.success) {
289
- return
290
- }
291
-
292
- expect(parsed.error.issues).toEqual(
293
- expect.arrayContaining([
294
- expect.objectContaining({
295
- message:
296
- 'Invalid locale "fr". Inline translations only support configured site locales: en, de',
297
- path: ["title", "fr"],
298
- }),
299
- ]),
300
- )
301
- })
302
-
303
- test("Should reject unsupported locale in language label", () => {
304
- const parsed = extendedConfigSchema.safeParse({
305
- ...requiredConfig,
306
- languages: [
307
- {
308
- code: "en",
309
- label: { en: "English", fr: "Anglais" },
310
- isDefaultSiteLanguage: true,
311
- },
312
- ],
313
- })
314
-
315
- expect(parsed.success).toBe(false)
316
- if (parsed.success) {
317
- return
318
- }
319
-
320
- expect(parsed.error.issues).toEqual(
321
- expect.arrayContaining([
322
- expect.objectContaining({
323
- message:
324
- 'Invalid locale "fr". Inline translations only support configured site locales: en',
325
- path: ["languages", 0, "label", "fr"],
326
- }),
327
- ]),
328
- )
329
- })
330
-
331
- test("Should accept config when inline translations define default locale", () => {
332
- const config = configSchema.parse({
333
- ...requiredConfig,
334
- languages: [
335
- requiredConfig.languages[0],
336
- {
337
- code: "de",
338
- label: { en: "German" },
339
- isSiteLanguage: true,
340
- fallbackLanguages: ["fr"],
341
- },
342
- ],
343
- title: {
344
- en: "LightNet",
345
- },
346
- logo: {
347
- src: "/src/assets/logo.png",
348
- alt: {
349
- en: "LightNet logo",
350
- },
351
- },
352
- mainMenu: [
353
- {
354
- href: "/about",
355
- label: {
356
- en: "About",
357
- },
358
- requiresLocale: true,
359
- },
360
- ],
361
- })
362
-
363
- expect(config).toBeDefined()
364
- })
@@ -1,125 +0,0 @@
1
- import { expect, test, vi } from "vitest"
2
-
3
- import { lightnet } from "../../src/astro-integration/integration"
4
-
5
- const requiredLightnetConfig = {
6
- title: { en: "LightNet", de: "LightNet" },
7
- languages: [
8
- {
9
- code: "en",
10
- label: { en: "English" },
11
- isDefaultSiteLanguage: true,
12
- },
13
- {
14
- code: "de",
15
- label: { en: "German" },
16
- isSiteLanguage: true,
17
- },
18
- ],
19
- }
20
-
21
- const runSetup = ({
22
- lightnetConfig = {},
23
- astroSite,
24
- }: {
25
- lightnetConfig?: Record<string, unknown>
26
- astroSite?: string | URL
27
- }) => {
28
- const integration = lightnet({
29
- ...requiredLightnetConfig,
30
- ...lightnetConfig,
31
- })
32
- const setupHook = integration.hooks["astro:config:setup"]!
33
- const updateConfig = vi.fn()
34
-
35
- setupHook({
36
- injectRoute: vi.fn(),
37
- config: {
38
- integrations: [],
39
- root: new URL("file:///tmp/lightnet"),
40
- srcDir: new URL("file:///tmp/lightnet/src"),
41
- site: astroSite,
42
- } as never,
43
- updateConfig,
44
- logger: {
45
- info: vi.fn(),
46
- warn: vi.fn(),
47
- error: vi.fn(),
48
- debug: vi.fn(),
49
- } as never,
50
- addMiddleware: vi.fn(),
51
- } as never)
52
-
53
- return { updateConfig }
54
- }
55
-
56
- const getThrownError = (run: () => void) => {
57
- try {
58
- run()
59
- } catch (error) {
60
- return error as Error & { hint?: string }
61
- }
62
- throw new Error("Expected function to throw")
63
- }
64
-
65
- test("Should use lightnet.site and not inject Astro i18n config", () => {
66
- const { updateConfig } = runSetup({
67
- astroSite: "https://lightnet.community",
68
- })
69
-
70
- expect(updateConfig).toHaveBeenCalledWith({
71
- integrations: expect.arrayContaining([
72
- expect.objectContaining({ name: "@lightnet/tailwind" }),
73
- expect.objectContaining({ name: "@astrojs/react" }),
74
- ]),
75
- vite: {
76
- plugins: expect.any(Array),
77
- },
78
- })
79
- expect(updateConfig.mock.calls[0][0]).not.toHaveProperty("site")
80
- expect(updateConfig.mock.calls[0][0]).not.toHaveProperty("i18n")
81
- })
82
-
83
- test("Should use Astro site when lightnet.site is not set", () => {
84
- const { updateConfig } = runSetup({
85
- astroSite: "https://lightnet.community",
86
- })
87
-
88
- expect(updateConfig).toHaveBeenCalledWith({
89
- integrations: expect.arrayContaining([
90
- expect.objectContaining({ name: "@lightnet/tailwind" }),
91
- expect.objectContaining({ name: "@astrojs/react" }),
92
- ]),
93
- vite: {
94
- plugins: expect.any(Array),
95
- },
96
- })
97
- expect(updateConfig.mock.calls[0][0]).not.toHaveProperty("site")
98
- })
99
-
100
- test("Should fail schema validation when no site is set", () => {
101
- const error = getThrownError(() => runSetup({}))
102
-
103
- expect(error).toMatchObject({
104
- message: "Invalid LightNet configuration",
105
- hint: expect.stringContaining("Set `site` in your Astro config"),
106
- })
107
- })
108
-
109
- test("Should include path when inline translation misses default locale", () => {
110
- const error = getThrownError(() =>
111
- runSetup({
112
- lightnetConfig: {
113
- title: { de: "LightNet" },
114
- },
115
- astroSite: "https://lightnet.community",
116
- }),
117
- )
118
-
119
- expect(error).toMatchObject({
120
- message: "Invalid LightNet configuration",
121
- hint: expect.stringContaining(
122
- 'title.en: Missing translation for default locale "en"',
123
- ),
124
- })
125
- })
@@ -1,36 +0,0 @@
1
- import { expect, test, vi } from "vitest"
2
-
3
- import tailwind from "../../src/astro-integration/tailwind"
4
-
5
- test("Should preserve inline PostCSS options when injecting Tailwind", async () => {
6
- const existingPlugin = {
7
- postcssPlugin: "existing-plugin",
8
- Once: () => {},
9
- }
10
- const updateConfig = vi.fn()
11
-
12
- await tailwind().hooks["astro:config:setup"]?.({
13
- config: {
14
- root: new URL("file:///tmp/lightnet"),
15
- vite: {
16
- css: {
17
- postcss: {
18
- map: { inline: false },
19
- parser: "sugarss",
20
- plugins: [existingPlugin],
21
- },
22
- },
23
- },
24
- } as never,
25
- updateConfig,
26
- } as never)
27
-
28
- const viteConfig = updateConfig.mock.calls[0][0].vite
29
-
30
- expect(viteConfig.css.postcss).toMatchObject({
31
- map: { inline: false },
32
- parser: "sugarss",
33
- })
34
- expect(viteConfig.css.postcss.plugins).toHaveLength(3)
35
- expect(viteConfig.css.postcss.plugins[0]).toBe(existingPlugin)
36
- })
@@ -1,109 +0,0 @@
1
- import { z } from "astro/zod"
2
- import { expect, test, vi } from "vitest"
3
-
4
- vi.mock("astro:content", () => ({
5
- defineCollection: (definition: unknown) => definition,
6
- reference: () => z.string(),
7
- }))
8
-
9
- const {
10
- categorySchema,
11
- inlineTranslationSchema,
12
- mediaCollectionSchema,
13
- mediaItemSchema,
14
- } = await import("../../src/content/content-schema")
15
-
16
- test("Should accept inline translation with only default locale", () => {
17
- const parsed = inlineTranslationSchema.safeParse({
18
- en: "Hello",
19
- })
20
-
21
- expect(parsed.success).toBe(true)
22
- })
23
-
24
- test("Should accept inline translation without a specific default locale", () => {
25
- const parsed = inlineTranslationSchema.safeParse({
26
- de: "Hallo",
27
- })
28
-
29
- expect(parsed.success).toBe(true)
30
- })
31
-
32
- test("Should accept arbitrary locale keys in inline translation", () => {
33
- const parsed = inlineTranslationSchema.safeParse({
34
- en: "Hello",
35
- fr: "Bonjour",
36
- })
37
-
38
- expect(parsed.success).toBe(true)
39
- })
40
-
41
- test("Should reject empty inline translations", () => {
42
- const parsed = inlineTranslationSchema.safeParse({})
43
-
44
- expect(parsed.success).toBe(false)
45
- if (parsed.success) {
46
- return
47
- }
48
-
49
- expect(parsed.error.issues).toEqual(
50
- expect.arrayContaining([
51
- expect.objectContaining({
52
- message: "Inline translations must contain at least one entry",
53
- path: [],
54
- }),
55
- ]),
56
- )
57
- })
58
-
59
- test("Should accept category label with default locale only", () => {
60
- const parsed = categorySchema.safeParse({
61
- label: {
62
- en: "Category",
63
- },
64
- })
65
-
66
- expect(parsed.success).toBe(true)
67
- })
68
-
69
- test("Should accept media collection with media item references", () => {
70
- const parsed = mediaCollectionSchema.safeParse({
71
- label: {
72
- en: "Collection",
73
- },
74
- mediaItems: ["book-a--en", "book-b--en"],
75
- })
76
-
77
- expect(parsed.success).toBe(true)
78
- })
79
-
80
- test("Should reject media collection without media item references", () => {
81
- const parsed = mediaCollectionSchema.safeParse({
82
- label: {
83
- en: "Collection",
84
- },
85
- })
86
-
87
- expect(parsed.success).toBe(false)
88
- })
89
-
90
- test("Should accept media item without commonId", () => {
91
- const parsed = mediaItemSchema.safeParse({
92
- title: "A book about love",
93
- type: "book",
94
- description: "Description",
95
- authors: ["George Miller"],
96
- dateCreated: "2024-09-10",
97
- categories: ["family"],
98
- language: "en",
99
- image: {
100
- src: "/images/a-book-about-love--en.jpg",
101
- width: 600,
102
- height: 900,
103
- format: "webp",
104
- },
105
- content: [{ type: "upload", url: "/files/a-book-about-love.pdf" }],
106
- })
107
-
108
- expect(parsed.success).toBe(true)
109
- })
@@ -1,72 +0,0 @@
1
- import { beforeEach, expect, test, vi } from "vitest"
2
-
3
- const getCollectionMock = vi.fn()
4
-
5
- vi.mock("astro:content", async () => {
6
- const { z } = await import("astro/zod")
7
-
8
- return {
9
- getCollection: getCollectionMock,
10
- defineCollection: (definition: unknown) => definition,
11
- reference: () => z.object({ id: z.string(), collection: z.string() }),
12
- }
13
- })
14
-
15
- beforeEach(() => {
16
- vi.resetModules()
17
- getCollectionMock.mockReset()
18
- })
19
-
20
- test("Should build a reverse index and ignore duplicate media items in a collection", async () => {
21
- getCollectionMock.mockResolvedValue([
22
- {
23
- id: "series-a",
24
- data: {
25
- label: { en: "Series A" },
26
- mediaItems: [
27
- { id: "item-2", collection: "media" },
28
- { id: "item-1", collection: "media" },
29
- { id: "item-1", collection: "media" },
30
- ],
31
- },
32
- },
33
- {
34
- id: "series-b",
35
- data: {
36
- label: { en: "Series B" },
37
- mediaItems: [{ id: "item-1", collection: "media" }],
38
- },
39
- },
40
- ])
41
-
42
- const { getCollectionsForMediaItem } =
43
- await import("../../src/content/get-media-collections")
44
-
45
- expect(await getCollectionsForMediaItem("item-1")).toEqual([
46
- "series-a",
47
- "series-b",
48
- ])
49
- expect(await getCollectionsForMediaItem("item-2")).toEqual(["series-a"])
50
- })
51
-
52
- test("Should return empty for unknown media ids and load collections once", async () => {
53
- getCollectionMock.mockResolvedValue([
54
- {
55
- id: "series-a",
56
- data: {
57
- label: { en: "Series A" },
58
- mediaItems: [
59
- { id: "item-2", collection: "media" },
60
- { id: "item-1", collection: "media" },
61
- ],
62
- },
63
- },
64
- ])
65
-
66
- const { getCollectionsForMediaItem } =
67
- await import("../../src/content/get-media-collections")
68
-
69
- expect(await getCollectionsForMediaItem("missing-id")).toEqual([])
70
- expect(await getCollectionsForMediaItem("item-1")).toEqual(["series-a"])
71
- expect(getCollectionMock).toHaveBeenCalledTimes(1)
72
- })