@raketa-cloud/admin 1.9.4 → 1.9.5

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.
@@ -68,6 +68,72 @@ describe("Resource Translations Handler", () => {
68
68
  });
69
69
  });
70
70
 
71
+ test("create fails with locale errors segmented by locale", async () => {
72
+ const resource = Factory.build("resource", {
73
+ translations: {},
74
+ categories: [{ id: "cat1" }],
75
+ });
76
+ const params = {
77
+ languages: ["fr", "es"],
78
+ };
79
+
80
+ const ctx = buildCtx(params, resource);
81
+
82
+ jest.spyOn(ctx.client.resources, "find").mockImplementation(() => resource);
83
+
84
+ const localeErrors = [
85
+ {
86
+ locale: "fr",
87
+ errors: [{ key: "title", message: "cannot be blank" }],
88
+ },
89
+ {
90
+ locale: "es",
91
+ errors: [{ key: "slug", message: "already taken" }],
92
+ },
93
+ ];
94
+
95
+ jest
96
+ .spyOn(ctx.client.resources, "multiTranslate")
97
+ .mockImplementation(() => ({
98
+ resources: null,
99
+ errors: null,
100
+ localeErrors,
101
+ }));
102
+
103
+ const result = await TranslationsHandler.create(ctx);
104
+
105
+ expect(result).toEqual({
106
+ assigns: {
107
+ resource: {
108
+ title: resource.title,
109
+ tags: [],
110
+ visibility: "private",
111
+ slug: resource.slug,
112
+ settings: resource.settings,
113
+ widgets: resource.widgets,
114
+ categories: ["cat1"],
115
+ translations: {},
116
+ languages: ["fr", "es"],
117
+ },
118
+ blueprint: resource,
119
+ targetLocale: "en",
120
+ errors: {
121
+ resource: {},
122
+ localeErrors: [
123
+ {
124
+ locale: "fr",
125
+ errors: { title: "cannot be blank" },
126
+ },
127
+ {
128
+ locale: "es",
129
+ errors: { slug: "already taken" },
130
+ },
131
+ ],
132
+ },
133
+ },
134
+ });
135
+ });
136
+
71
137
  test("create fails with translation errors", async () => {
72
138
  const resource = Factory.build("resource", {
73
139
  translations: {},
@@ -84,14 +150,12 @@ describe("Resource Translations Handler", () => {
84
150
  .mockImplementation(() => resource);
85
151
 
86
152
  const translationErrors = [{ key: "title", message: "cannot be blank" }];
87
- const mockTranslateBatch = jest
88
- .spyOn(ctx.client.resources, "translateBatch")
89
- .mockImplementation((parentId, locales) =>
90
- locales.map(() => ({
91
- resource: null,
92
- errors: translationErrors,
93
- })),
94
- );
153
+ const mockMultiTranslate = jest
154
+ .spyOn(ctx.client.resources, "multiTranslate")
155
+ .mockImplementation(() => ({
156
+ resources: null,
157
+ errors: translationErrors,
158
+ }));
95
159
 
96
160
  const mockFormatErrors = jest
97
161
  .spyOn(ctx.client.resources, "formatErrors")
@@ -100,7 +164,7 @@ describe("Resource Translations Handler", () => {
100
164
  const result = await TranslationsHandler.create(ctx);
101
165
 
102
166
  expect(mockFind).toHaveBeenCalledWith(resource.id, "en", "article");
103
- expect(mockTranslateBatch).toHaveBeenCalled();
167
+ expect(mockMultiTranslate).toHaveBeenCalled();
104
168
  expect(mockFormatErrors).toHaveBeenCalledWith(translationErrors);
105
169
 
106
170
  expect(result).toEqual({
@@ -117,7 +181,7 @@ describe("Resource Translations Handler", () => {
117
181
  languages: ["fr"],
118
182
  },
119
183
  blueprint: resource,
120
- targetLocale: "fr",
184
+ targetLocale: "en",
121
185
  errors: { resource: { title: "cannot be blank" } },
122
186
  },
123
187
  });
@@ -139,14 +203,14 @@ describe("Resource Translations Handler", () => {
139
203
  .spyOn(ctx.client.resources, "find")
140
204
  .mockImplementation(() => resource);
141
205
 
142
- const mockTranslateBatch = jest
143
- .spyOn(ctx.client.resources, "translateBatch")
144
- .mockImplementation((parentId, locales) =>
145
- locales.map((locale) => ({
146
- resource: { id: `translated-resource-${locale}` },
147
- errors: null,
206
+ const mockMultiTranslate = jest
207
+ .spyOn(ctx.client.resources, "multiTranslate")
208
+ .mockImplementation((parentId, locales) => ({
209
+ resources: locales.map((locale) => ({
210
+ id: `translated-resource-${locale}`,
148
211
  })),
149
- );
212
+ errors: null,
213
+ }));
150
214
 
151
215
  const mockCreateRevisionBatch = jest
152
216
  .spyOn(ctx.client.resourceRevisions, "createBatch")
@@ -161,8 +225,8 @@ describe("Resource Translations Handler", () => {
161
225
 
162
226
  expect(mockFind).toHaveBeenCalledWith(resource.id, "en", "article");
163
227
 
164
- expect(mockTranslateBatch).toHaveBeenCalledTimes(1);
165
- expect(mockTranslateBatch).toHaveBeenCalledWith(
228
+ expect(mockMultiTranslate).toHaveBeenCalledTimes(1);
229
+ expect(mockMultiTranslate).toHaveBeenCalledWith(
166
230
  resource.id,
167
231
  ["fr", "es"],
168
232
  expect.objectContaining({
@@ -173,6 +237,7 @@ describe("Resource Translations Handler", () => {
173
237
  }),
174
238
  "article",
175
239
  undefined,
240
+ "en",
176
241
  );
177
242
 
178
243
  expect(mockCreateRevisionBatch).toHaveBeenCalledTimes(1);
@@ -210,22 +275,12 @@ describe("Resource Translations Handler", () => {
210
275
  .spyOn(ctx.client.resources, "find")
211
276
  .mockImplementation(() => resource);
212
277
 
213
- const mockTranslateBatch = jest
214
- .spyOn(ctx.client.resources, "translateBatch")
215
- .mockImplementation(() => [
216
- {
217
- resource: {
218
- id: "translated-resource-fr",
219
- slug: "resource-slug",
220
- visibility: "public",
221
- },
222
- errors: null,
223
- },
224
- {
225
- resource: null,
226
- errors: [{ key: "title", message: "invalid for spanish" }],
227
- },
228
- ]);
278
+ const mockMultiTranslate = jest
279
+ .spyOn(ctx.client.resources, "multiTranslate")
280
+ .mockImplementation(() => ({
281
+ resources: null,
282
+ errors: [{ key: "title", message: "invalid for spanish" }],
283
+ }));
229
284
 
230
285
  jest
231
286
  .spyOn(ctx.client.resources, "formatErrors")
@@ -235,7 +290,7 @@ describe("Resource Translations Handler", () => {
235
290
 
236
291
  expect(mockFind).toHaveBeenCalledWith(resource.id, "en", "article");
237
292
 
238
- expect(mockTranslateBatch).toHaveBeenCalledWith(
293
+ expect(mockMultiTranslate).toHaveBeenCalledWith(
239
294
  resource.id,
240
295
  ["fr", "es"],
241
296
  expect.objectContaining({
@@ -246,6 +301,7 @@ describe("Resource Translations Handler", () => {
246
301
  }),
247
302
  "article",
248
303
  undefined,
304
+ "en",
249
305
  );
250
306
 
251
307
  expect(result).toEqual({
@@ -258,17 +314,11 @@ describe("Resource Translations Handler", () => {
258
314
  settings: resource.settings,
259
315
  widgets: resource.widgets,
260
316
  categories: ["some-category-uno"],
261
- translations: {
262
- fr: {
263
- id: "translated-resource-fr",
264
- slug: resource.slug,
265
- visibility: resource.visibility,
266
- },
267
- },
317
+ translations: {},
268
318
  languages: ["fr", "es"],
269
319
  },
270
320
  blueprint: resource,
271
- targetLocale: "es",
321
+ targetLocale: "en",
272
322
  errors: { resource: { title: "invalid for spanish" } },
273
323
  },
274
324
  });
@@ -288,17 +338,17 @@ describe("Resource Translations Handler", () => {
288
338
  jest.spyOn(ctx.client.resources, "find").mockImplementation(() => resource);
289
339
 
290
340
  jest
291
- .spyOn(ctx.client.resources, "translateBatch")
292
- .mockImplementation(() => [
293
- {
294
- resource: {
341
+ .spyOn(ctx.client.resources, "multiTranslate")
342
+ .mockImplementation(() => ({
343
+ resources: [
344
+ {
295
345
  id: "translated-resource-fr",
296
346
  slug: "resource-slug",
297
347
  visibility: "public",
298
348
  },
299
- errors: null,
300
- },
301
- ]);
349
+ ],
350
+ errors: null,
351
+ }));
302
352
 
303
353
  const revisionErrors = [{ key: "base", message: "something went wrong" }];
304
354
  const mockCreateRevisionBatch = jest
@@ -365,12 +415,14 @@ describe("Resource Translations Handler", () => {
365
415
  const mockQuery = jest
366
416
  .spyOn(ctx.client.resources, "query")
367
417
  .mockImplementation(() => ({
368
- translate0: {
369
- resource: {
370
- id: "translated-resource-fr",
371
- slug: "resource-slug",
372
- visibility: "public",
373
- },
418
+ multiTranslateResource: {
419
+ resources: [
420
+ {
421
+ id: "translated-resource-fr",
422
+ slug: "resource-slug",
423
+ visibility: "public",
424
+ },
425
+ ],
374
426
  errors: null,
375
427
  },
376
428
  }));
@@ -378,23 +430,27 @@ describe("Resource Translations Handler", () => {
378
430
  const result = await TranslationsHandler.create(ctx);
379
431
 
380
432
  expect(mockQuery).toHaveBeenCalledWith(expect.any(String), {
381
- input0: {
382
- locale: "fr",
383
- resource: {
384
- categories: ["cat1"],
385
- resourceType: "article",
386
- settings:
387
- '{"layout":"default","meta":{"title":"Open graph","description":"Open graph???","image":{}}}',
388
- slug: "resource-slug",
389
- tags: [],
390
- title: "Resource Title",
391
- visibility: "private",
392
- widgets: "[]",
393
- },
433
+ input: {
394
434
  sourceResourceId: "2",
435
+ translations: [
436
+ {
437
+ locale: "fr",
438
+ resource: {
439
+ categories: ["cat1"],
440
+ resourceType: "article",
441
+ settings:
442
+ '{"layout":"default","meta":{"title":"Open graph","description":"Open graph???","image":{}}}',
443
+ slug: "resource-slug",
444
+ tags: [],
445
+ title: "Resource Title",
446
+ visibility: "private",
447
+ widgets: "[]",
448
+ },
449
+ },
450
+ ],
395
451
  workflow: {
396
452
  previewUrl: expect.stringMatching(
397
- /^https:\/\/example\.com\/fr\/magic-preview\/articles\/[A-Za-z0-9_-]+$/,
453
+ /^https:\/\/example\.com\/en\/magic-preview\/articles\/[A-Za-z0-9_-]+$/,
398
454
  ),
399
455
  type: "LOKALISE_TT_AI",
400
456
  },
@@ -114,21 +114,25 @@ describe("Resource Translations Handler create", () => {
114
114
  });
115
115
 
116
116
  jest.spyOn(ctx.client.resources, "find").mockImplementation(() => resource);
117
- const mockBatch = jest
118
- .spyOn(ctx.client.resources, "translateBatch")
119
- .mockImplementation(() => [{ resource: { id: "fr-1" }, errors: null }]);
117
+ const mockMultiTranslate = jest
118
+ .spyOn(ctx.client.resources, "multiTranslate")
119
+ .mockImplementation(() => ({
120
+ resources: [{ id: "fr-1" }],
121
+ errors: null,
122
+ }));
120
123
  jest
121
124
  .spyOn(ctx.client.resourceRevisions, "createBatch")
122
125
  .mockImplementation(() => [{ errors: null }]);
123
126
 
124
127
  const result = await TranslationsHandler.create(ctx);
125
128
 
126
- expect(mockBatch).toHaveBeenCalledWith(
129
+ expect(mockMultiTranslate).toHaveBeenCalledWith(
127
130
  resource.id,
128
131
  ["fr"],
129
132
  expect.anything(),
130
133
  "article",
131
134
  undefined,
135
+ "en",
132
136
  );
133
137
  expect(result).toEqual({
134
138
  redirect: `/en/articles/${resource.id}/translations?translations=fr:fr-1`,
@@ -150,8 +154,11 @@ describe("Resource Translations Handler create", () => {
150
154
  .spyOn(ctx.client.resources, "find")
151
155
  .mockImplementation(() => blueprint);
152
156
  jest
153
- .spyOn(ctx.client.resources, "translateBatch")
154
- .mockImplementation(() => [{ resource: { id: "fr-1" }, errors: null }]);
157
+ .spyOn(ctx.client.resources, "multiTranslate")
158
+ .mockImplementation(() => ({
159
+ resources: [{ id: "fr-1" }],
160
+ errors: null,
161
+ }));
155
162
  const mockRevisions = jest.spyOn(
156
163
  ctx.client.resourceRevisions,
157
164
  "createBatch",
@@ -195,8 +202,8 @@ describe("Resource Translations Handler create", () => {
195
202
 
196
203
  jest.spyOn(ctx.client.resources, "find").mockImplementation(() => resource);
197
204
  jest
198
- .spyOn(ctx.client.resources, "translateBatch")
199
- .mockImplementation(() => null);
205
+ .spyOn(ctx.client.resources, "multiTranslate")
206
+ .mockImplementation(() => ({ resources: null, errors: null }));
200
207
  const mockFormatErrors = jest
201
208
  .spyOn(ctx.client.resources, "formatErrors")
202
209
  .mockImplementation(() => ({ languages: "translation failed" }));
@@ -210,4 +217,40 @@ describe("Resource Translations Handler create", () => {
210
217
  languages: "translation failed",
211
218
  });
212
219
  });
220
+
221
+ test("deduplicates languages when duplicate locales are provided in params", async () => {
222
+ const resource = Factory.build("resource", {
223
+ translations: {},
224
+ categories: [{ id: "cat1" }],
225
+ });
226
+ const ctx = buildCtx({
227
+ resource,
228
+ resourceParams: { resource: { languages: ["fr", "fr", "de"] } },
229
+ });
230
+
231
+ jest.spyOn(ctx.client.resources, "find").mockImplementation(() => resource);
232
+ const mockMultiTranslate = jest
233
+ .spyOn(ctx.client.resources, "multiTranslate")
234
+ .mockImplementation(() => ({
235
+ resources: [{ id: "fr-1" }, { id: "de-1" }],
236
+ errors: null,
237
+ }));
238
+ jest
239
+ .spyOn(ctx.client.resourceRevisions, "createBatch")
240
+ .mockImplementation(() => [{ errors: null }, { errors: null }]);
241
+
242
+ const result = await TranslationsHandler.create(ctx);
243
+
244
+ expect(mockMultiTranslate).toHaveBeenCalledWith(
245
+ resource.id,
246
+ ["fr", "de"],
247
+ expect.anything(),
248
+ "article",
249
+ undefined,
250
+ "en",
251
+ );
252
+ expect(result).toEqual({
253
+ redirect: `/en/articles/${resource.id}/translations?translations=fr:fr-1;de:de-1`,
254
+ });
255
+ });
213
256
  });
@@ -87,16 +87,18 @@ const CONTENT_TASK = {
87
87
  failedAt: null,
88
88
  insertedAt: "2024-08-30T19:05:00Z",
89
89
  updatedAt: "2024-08-30T19:05:00Z",
90
- resource: {
91
- id: "123",
92
- title: "Test Resource",
93
- slug: "test-resource",
94
- locale: "en",
95
- visibility: "public",
96
- resourceType: "page",
97
- insertedAt: "2024-08-30T19:00:00Z",
98
- updatedAt: "2024-08-30T19:05:00Z",
99
- },
90
+ resources: [
91
+ {
92
+ id: "123",
93
+ title: "Test Resource",
94
+ slug: "test-resource",
95
+ locale: "en",
96
+ visibility: "public",
97
+ resourceType: "page",
98
+ insertedAt: "2024-08-30T19:00:00Z",
99
+ updatedAt: "2024-08-30T19:05:00Z",
100
+ },
101
+ ],
100
102
  lokaliseWorkflow: null,
101
103
  };
102
104