@cosmicdrift/kumiko-renderer-web 0.147.3 → 0.148.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-renderer-web",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.148.0",
|
|
4
4
|
"description": "Web-platform bindings for @cosmicdrift/kumiko-renderer. HTML default-primitives, browser history-based navigation, EventSource-backed live events, and a one-call createKumikoApp that mounts the whole stack via react-dom.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
"./styles.css": "./src/styles.css"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@cosmicdrift/kumiko-dispatcher-live": "0.
|
|
20
|
-
"@cosmicdrift/kumiko-headless": "0.
|
|
21
|
-
"@cosmicdrift/kumiko-renderer": "0.
|
|
19
|
+
"@cosmicdrift/kumiko-dispatcher-live": "0.148.0",
|
|
20
|
+
"@cosmicdrift/kumiko-headless": "0.148.0",
|
|
21
|
+
"@cosmicdrift/kumiko-renderer": "0.148.0",
|
|
22
22
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
23
23
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
24
24
|
"@radix-ui/react-label": "^2.1.8",
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { afterAll, afterEach, describe, expect, spyOn, test } from "bun:test";
|
|
2
2
|
import type {
|
|
3
|
+
DashboardScreenDefinition,
|
|
3
4
|
EntityDefinition,
|
|
4
5
|
EntityEditScreenDefinition,
|
|
5
6
|
EntityListScreenDefinition,
|
|
6
7
|
} from "@cosmicdrift/kumiko-framework/ui-types";
|
|
7
8
|
import type { Dispatcher } from "@cosmicdrift/kumiko-headless";
|
|
8
9
|
import type { ColumnRendererProps, FeatureSchema, NavApi } from "@cosmicdrift/kumiko-renderer";
|
|
10
|
+
import { createStaticLocaleResolver } from "@cosmicdrift/kumiko-renderer";
|
|
9
11
|
import { act, screen, waitFor } from "@testing-library/react";
|
|
10
12
|
import type { ReactNode } from "react";
|
|
11
13
|
import type { ClientFeatureDefinition } from "../app/client-plugin";
|
|
@@ -252,6 +254,99 @@ describe("createKumikoApp", () => {
|
|
|
252
254
|
expect(screen.getByTestId("ca-swatch-field").textContent).toBe("color");
|
|
253
255
|
});
|
|
254
256
|
|
|
257
|
+
test("schema.translations (r.translations, #1059) resolves nav/dashboard labels without any clientFeatures duplication", async () => {
|
|
258
|
+
// Mirrors cap-counter's real shape: a feature that declares r.translations
|
|
259
|
+
// for its own nav label but ships NO web/i18n.ts / clientFeatures entry at
|
|
260
|
+
// all. Before #1059 this label rendered as the raw i18n key forever.
|
|
261
|
+
const dashboardScreen: DashboardScreenDefinition = {
|
|
262
|
+
id: "overview",
|
|
263
|
+
type: "dashboard",
|
|
264
|
+
panels: [
|
|
265
|
+
{
|
|
266
|
+
kind: "stat",
|
|
267
|
+
id: "cap-list",
|
|
268
|
+
label: "cap-counter:nav.cap-list",
|
|
269
|
+
query: "cap-counter:query:stat",
|
|
270
|
+
valueField: "value",
|
|
271
|
+
},
|
|
272
|
+
],
|
|
273
|
+
};
|
|
274
|
+
const schema: FeatureSchema = {
|
|
275
|
+
featureName: "cap-counter",
|
|
276
|
+
entities: {},
|
|
277
|
+
screens: [dashboardScreen],
|
|
278
|
+
translations: {
|
|
279
|
+
"cap-counter:nav.cap-list": { de: "Limits", en: "Caps" },
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
const dispatcher = createMockDispatcher({
|
|
283
|
+
query: (async () => ({
|
|
284
|
+
isSuccess: true,
|
|
285
|
+
data: { value: "3" },
|
|
286
|
+
})) as unknown as Dispatcher["query"],
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
mountRoot();
|
|
290
|
+
await mountApp({
|
|
291
|
+
schema,
|
|
292
|
+
dispatcher,
|
|
293
|
+
locale: createStaticLocaleResolver({ locale: "en" }),
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
await waitFor(() => expect(screen.getByText("Caps")).toBeTruthy());
|
|
297
|
+
expect(screen.queryByText("cap-counter:nav.cap-list")).toBeNull();
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test("clientFeatures.translations still overrides schema.translations for the same key (precedence)", async () => {
|
|
301
|
+
// App-Override muss auch gegen ein framework-eigenes Label gewinnen —
|
|
302
|
+
// sonst kann eine App ein bundled-feature-Label nicht mehr anpassen,
|
|
303
|
+
// sobald #1059 dessen r.translations automatisch mitliefert.
|
|
304
|
+
const dashboardScreen: DashboardScreenDefinition = {
|
|
305
|
+
id: "overview",
|
|
306
|
+
type: "dashboard",
|
|
307
|
+
panels: [
|
|
308
|
+
{
|
|
309
|
+
kind: "stat",
|
|
310
|
+
id: "cap-list",
|
|
311
|
+
label: "cap-counter:nav.cap-list",
|
|
312
|
+
query: "cap-counter:query:stat",
|
|
313
|
+
valueField: "value",
|
|
314
|
+
},
|
|
315
|
+
],
|
|
316
|
+
};
|
|
317
|
+
const schema: FeatureSchema = {
|
|
318
|
+
featureName: "cap-counter",
|
|
319
|
+
entities: {},
|
|
320
|
+
screens: [dashboardScreen],
|
|
321
|
+
translations: {
|
|
322
|
+
"cap-counter:nav.cap-list": { de: "Limits", en: "Caps" },
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
const dispatcher = createMockDispatcher({
|
|
326
|
+
query: (async () => ({
|
|
327
|
+
isSuccess: true,
|
|
328
|
+
data: { value: "3" },
|
|
329
|
+
})) as unknown as Dispatcher["query"],
|
|
330
|
+
});
|
|
331
|
+
const override: ClientFeatureDefinition = {
|
|
332
|
+
name: "cap-counter-app-override",
|
|
333
|
+
translations: {
|
|
334
|
+
en: { "cap-counter:nav.cap-list": "Quota" },
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
mountRoot();
|
|
339
|
+
await mountApp({
|
|
340
|
+
schema,
|
|
341
|
+
dispatcher,
|
|
342
|
+
clientFeatures: [override],
|
|
343
|
+
locale: createStaticLocaleResolver({ locale: "en" }),
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
await waitFor(() => expect(screen.getByText("Quota")).toBeTruthy());
|
|
347
|
+
expect(screen.queryByText("Caps")).toBeNull();
|
|
348
|
+
});
|
|
349
|
+
|
|
255
350
|
test("navAdapter override: eigener Router steuert den aktiven Screen", async () => {
|
|
256
351
|
// Beweist den Nav-Seam: der Default-Adapter liest location.pathname,
|
|
257
352
|
// dieser Memory-Adapter hardcoded die Route. Wenn swap funktioniert,
|
package/src/app/create-app.tsx
CHANGED
|
@@ -20,13 +20,16 @@ import {
|
|
|
20
20
|
kumikoDefaultTranslations,
|
|
21
21
|
LiveEventsProvider,
|
|
22
22
|
LocaleProvider,
|
|
23
|
+
mergeTranslations,
|
|
23
24
|
type NavApi,
|
|
24
25
|
NavProvider,
|
|
25
26
|
PrimitivesProvider,
|
|
26
27
|
type PrimitivesRegistry,
|
|
27
28
|
qualifyScreenId,
|
|
28
29
|
TokensProvider,
|
|
30
|
+
type TranslationsByLocale,
|
|
29
31
|
toAppSchema,
|
|
32
|
+
translationsByLocaleFromKeys,
|
|
30
33
|
useNav,
|
|
31
34
|
} from "@cosmicdrift/kumiko-renderer";
|
|
32
35
|
import { type ComponentType, type ReactNode, useMemo } from "react";
|
|
@@ -177,12 +180,24 @@ export function createKumikoApp(options: CreateKumikoAppOptions = {}): { readonl
|
|
|
177
180
|
const clientFeatures = options.clientFeatures ?? [];
|
|
178
181
|
const providers = clientFeatures.flatMap((f) => f.providers ?? []);
|
|
179
182
|
const gates = clientFeatures.flatMap((f) => f.gates ?? []);
|
|
180
|
-
//
|
|
181
|
-
// clientFeatures.translations
|
|
182
|
-
//
|
|
183
|
-
//
|
|
183
|
+
// Precedence in fallbackBundles (Array-Order = Priorität, höchste zuerst):
|
|
184
|
+
// 1. clientFeatures.translations — App-Overrides gewinnen immer, auch
|
|
185
|
+
// gegen framework-eigene Labels.
|
|
186
|
+
// 2. schemaTranslations — server-authored r.translations, von
|
|
187
|
+
// buildAppSchema verbatim projiziert (#1059). Ohne dieses Bundle
|
|
188
|
+
// resolven Nav-/Screen-Labels nur, wenn eine App sie ZUSÄTZLICH in
|
|
189
|
+
// web/i18n.ts dupliziert — die meisten bundled-features taten das
|
|
190
|
+
// nie, Labels rendern dann als rohe i18n-Keys.
|
|
191
|
+
// 3. kumikoDefaultTranslations — Framework-Defaults, ALLERLETZTER
|
|
192
|
+
// Fallback.
|
|
193
|
+
const schemaTranslations = app.features.reduce<TranslationsByLocale>(
|
|
194
|
+
(acc, f) =>
|
|
195
|
+
f.translations ? mergeTranslations(acc, translationsByLocaleFromKeys(f.translations)) : acc,
|
|
196
|
+
{},
|
|
197
|
+
);
|
|
184
198
|
const fallbackBundles = [
|
|
185
199
|
...clientFeatures.flatMap((f) => (f.translations !== undefined ? [f.translations] : [])),
|
|
200
|
+
schemaTranslations,
|
|
186
201
|
kumikoDefaultTranslations,
|
|
187
202
|
];
|
|
188
203
|
// Custom-Screen-Components-Map mergen: spätere Features überschreiben
|