@raketa-cloud/admin 0.0.69 → 0.0.70
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 +1 -1
- package/src/handlers/articles/TranslationsHandler.js +129 -44
- package/src/handlers/events/TranslationsHandler.js +132 -49
- package/src/tests/handlers/articles/TranslationHandler.test/create.test.js +291 -0
- package/src/tests/handlers/articles/TranslationHandler.test/create_form.test.js +71 -0
- package/src/tests/handlers/articles/TranslationHandler.test/index.test.js +91 -0
- package/src/tests/handlers/events/TranslationHandler.test/create.test.js +297 -0
- package/src/tests/handlers/events/TranslationHandler.test/create_form.test.js +71 -0
- package/src/tests/handlers/events/TranslationHandler.test/index.test.js +91 -0
- package/src/tests/handlers/pages/TranslationHandler.test/create.test.js +1 -1
- package/src/views/articles/_shared/_history_toolbar.ejs +2 -2
- package/src/views/articles/show.ejs +15 -4
- package/src/views/articles/translations/_translation_toolbar.ejs +18 -0
- package/src/views/articles/translations/create_form.ejs +68 -4
- package/src/views/articles/translations/index.ejs +39 -0
- package/src/views/events/_shared/_history_toolbar.ejs +2 -2
- package/src/views/events/show.ejs +15 -4
- package/src/views/events/translations/_translation_toolbar.ejs +18 -0
- package/src/views/events/translations/create_form.ejs +68 -4
- package/src/views/events/translations/index.ejs +39 -0
- package/src/views/pages/_shared/_history_toolbar.ejs +2 -2
- package/src/views/pages/show.ejs +2 -4
- package/src/views/pages/translations/_translation_toolbar.ejs +18 -0
- package/src/views/pages/translations/index.ejs +2 -19
- package/src/views/articles/translations/_form.ejs +0 -31
- package/src/views/events/translations/_form.ejs +0 -33
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import ArticleRevisions from "../../../../cloud/ArticleRevisions";
|
|
2
|
+
import Articles from "../../../../cloud/Articles";
|
|
3
|
+
import TranslationsHandler from "../../../../handlers/articles/TranslationsHandler";
|
|
4
|
+
import Factory from "../../../support/factory";
|
|
5
|
+
|
|
6
|
+
function buildCtx(params, article) {
|
|
7
|
+
return {
|
|
8
|
+
client: {
|
|
9
|
+
articles: new Articles(() => ({}), "token"),
|
|
10
|
+
articleRevisions: new ArticleRevisions(() => ({}), "token"),
|
|
11
|
+
},
|
|
12
|
+
params: { parent_id: article.id },
|
|
13
|
+
resourceParams: {
|
|
14
|
+
resource: params,
|
|
15
|
+
},
|
|
16
|
+
locale: "en",
|
|
17
|
+
resourceConfig: {
|
|
18
|
+
articleType: "article",
|
|
19
|
+
articleTypePlural: "articles",
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe("Articles Translation Handler", () => {
|
|
25
|
+
test("create fails with translation errors", async () => {
|
|
26
|
+
const article = Factory.build("article", {
|
|
27
|
+
translations: {},
|
|
28
|
+
categories: [{ id: "cat1" }],
|
|
29
|
+
});
|
|
30
|
+
const params = {
|
|
31
|
+
languages: ["fr"],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const ctx = buildCtx(params, article);
|
|
35
|
+
|
|
36
|
+
const mockFind = jest
|
|
37
|
+
.spyOn(ctx.client.articles, "find")
|
|
38
|
+
.mockImplementation(() => article);
|
|
39
|
+
|
|
40
|
+
const translationErrors = [{ key: "title", message: "cannot be blank" }];
|
|
41
|
+
const mockTranslate = jest
|
|
42
|
+
.spyOn(ctx.client.articles, "translate")
|
|
43
|
+
.mockImplementation(() => ({
|
|
44
|
+
article: null,
|
|
45
|
+
errors: translationErrors,
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
const mockFormatErrors = jest
|
|
49
|
+
.spyOn(ctx.client.articles, "formatErrors")
|
|
50
|
+
.mockImplementation(() => ({ title: "cannot be blank" }));
|
|
51
|
+
|
|
52
|
+
const result = await TranslationsHandler.create(ctx);
|
|
53
|
+
|
|
54
|
+
expect(mockFind).toHaveBeenCalledWith(article.id, "en");
|
|
55
|
+
expect(mockTranslate).toHaveBeenCalled();
|
|
56
|
+
expect(mockFormatErrors).toHaveBeenCalledWith(translationErrors);
|
|
57
|
+
|
|
58
|
+
expect(result).toEqual({
|
|
59
|
+
assigns: {
|
|
60
|
+
resource: {
|
|
61
|
+
title: article.title,
|
|
62
|
+
tags: [],
|
|
63
|
+
visibility: "private",
|
|
64
|
+
slug: article.slug,
|
|
65
|
+
settings: article.settings,
|
|
66
|
+
widgets: article.widgets,
|
|
67
|
+
categories: ["cat1"],
|
|
68
|
+
publishAt: article.publishAt,
|
|
69
|
+
translations: {},
|
|
70
|
+
languages: ["fr"],
|
|
71
|
+
},
|
|
72
|
+
blueprint: article,
|
|
73
|
+
targetLocale: "fr",
|
|
74
|
+
errors: { resource: { title: "cannot be blank" } },
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("create successfully multiple translations with already one is existing", async () => {
|
|
80
|
+
const article = Factory.build("article", {
|
|
81
|
+
translations: { de: "de-id" },
|
|
82
|
+
categories: [{ id: "cat1" }],
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const params = {
|
|
86
|
+
languages: ["fr", "es", "de"],
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const ctx = buildCtx(params, article);
|
|
90
|
+
|
|
91
|
+
const mockFind = jest
|
|
92
|
+
.spyOn(ctx.client.articles, "find")
|
|
93
|
+
.mockImplementation(() => article);
|
|
94
|
+
|
|
95
|
+
const mockTranslate = jest
|
|
96
|
+
.spyOn(ctx.client.articles, "translate")
|
|
97
|
+
.mockImplementation((parentId, locale) => ({
|
|
98
|
+
article: { id: `translated-article-${locale}` },
|
|
99
|
+
errors: null,
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
const mockCreateRevision = jest
|
|
103
|
+
.spyOn(ctx.client.articleRevisions, "create")
|
|
104
|
+
.mockImplementation(() => ({ errors: null }));
|
|
105
|
+
|
|
106
|
+
const result = await TranslationsHandler.create(ctx);
|
|
107
|
+
|
|
108
|
+
expect(mockFind).toHaveBeenCalledWith(article.id, "en");
|
|
109
|
+
|
|
110
|
+
expect(mockTranslate).toHaveBeenCalledTimes(2);
|
|
111
|
+
expect(mockTranslate).toHaveBeenCalledWith(
|
|
112
|
+
article.id,
|
|
113
|
+
"fr",
|
|
114
|
+
expect.objectContaining({
|
|
115
|
+
title: article.title,
|
|
116
|
+
tags: [],
|
|
117
|
+
visibility: "private",
|
|
118
|
+
slug: article.slug,
|
|
119
|
+
})
|
|
120
|
+
);
|
|
121
|
+
expect(mockTranslate).toHaveBeenCalledWith(
|
|
122
|
+
article.id,
|
|
123
|
+
"es",
|
|
124
|
+
expect.objectContaining({
|
|
125
|
+
title: article.title,
|
|
126
|
+
tags: [],
|
|
127
|
+
visibility: "private",
|
|
128
|
+
slug: article.slug,
|
|
129
|
+
})
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
expect(mockCreateRevision).toHaveBeenCalledTimes(2);
|
|
133
|
+
expect(mockCreateRevision).toHaveBeenCalledWith(
|
|
134
|
+
"translated-article-fr",
|
|
135
|
+
"fr"
|
|
136
|
+
);
|
|
137
|
+
expect(mockCreateRevision).toHaveBeenCalledWith(
|
|
138
|
+
"translated-article-es",
|
|
139
|
+
"es"
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
expect(result).toEqual({
|
|
143
|
+
redirect: `/en/articles/${article.id}/translations?translations=fr:translated-article-fr;es:translated-article-es`,
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("create translations fails if any translation fails", async () => {
|
|
148
|
+
const article = Factory.build("article", {
|
|
149
|
+
translations: {},
|
|
150
|
+
categories: [{ id: "some-category-uno" }],
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const params = {
|
|
154
|
+
languages: ["fr", "es"],
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const ctx = buildCtx(params, article);
|
|
158
|
+
|
|
159
|
+
const mockFind = jest
|
|
160
|
+
.spyOn(ctx.client.articles, "find")
|
|
161
|
+
.mockImplementation(() => article);
|
|
162
|
+
|
|
163
|
+
const translationResponses = {
|
|
164
|
+
fr: { article: { id: "translated-article-fr" }, errors: null },
|
|
165
|
+
es: {
|
|
166
|
+
article: null,
|
|
167
|
+
errors: [{ key: "title", message: "invalid for spanish" }],
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const mockTranslate = jest
|
|
172
|
+
.spyOn(ctx.client.articles, "translate")
|
|
173
|
+
.mockImplementation((parentId, locale) => translationResponses[locale]);
|
|
174
|
+
|
|
175
|
+
const mockFormatErrors = jest
|
|
176
|
+
.spyOn(ctx.client.articles, "formatErrors")
|
|
177
|
+
.mockImplementation(() => ({
|
|
178
|
+
title: "invalid for spanish",
|
|
179
|
+
}));
|
|
180
|
+
|
|
181
|
+
const mockCreateRevision = jest
|
|
182
|
+
.spyOn(ctx.client.articleRevisions, "create")
|
|
183
|
+
.mockImplementation(() => ({ errors: null }));
|
|
184
|
+
|
|
185
|
+
const result = await TranslationsHandler.create(ctx);
|
|
186
|
+
|
|
187
|
+
expect(mockFind).toHaveBeenCalledWith(article.id, "en");
|
|
188
|
+
|
|
189
|
+
expect(mockTranslate).toHaveBeenCalledWith(
|
|
190
|
+
article.id,
|
|
191
|
+
"fr",
|
|
192
|
+
expect.objectContaining({
|
|
193
|
+
title: article.title,
|
|
194
|
+
tags: [],
|
|
195
|
+
visibility: "private",
|
|
196
|
+
slug: article.slug,
|
|
197
|
+
})
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
expect(mockTranslate).toHaveBeenCalledWith(
|
|
201
|
+
article.id,
|
|
202
|
+
"es",
|
|
203
|
+
expect.objectContaining({
|
|
204
|
+
title: article.title,
|
|
205
|
+
tags: [],
|
|
206
|
+
visibility: "private",
|
|
207
|
+
slug: article.slug,
|
|
208
|
+
})
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
expect(result).toEqual({
|
|
212
|
+
assigns: {
|
|
213
|
+
resource: {
|
|
214
|
+
title: article.title,
|
|
215
|
+
tags: [],
|
|
216
|
+
visibility: "private",
|
|
217
|
+
slug: article.slug,
|
|
218
|
+
settings: article.settings,
|
|
219
|
+
widgets: article.widgets,
|
|
220
|
+
categories: ["some-category-uno"],
|
|
221
|
+
publishAt: article.publishAt,
|
|
222
|
+
translations: {},
|
|
223
|
+
languages: ["fr", "es"],
|
|
224
|
+
},
|
|
225
|
+
blueprint: article,
|
|
226
|
+
targetLocale: "es",
|
|
227
|
+
errors: { resource: { title: "invalid for spanish" } },
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("create translation fails with revision errors", async () => {
|
|
233
|
+
const article = Factory.build("article", {
|
|
234
|
+
translations: {},
|
|
235
|
+
categories: [{ id: "cat1" }],
|
|
236
|
+
});
|
|
237
|
+
const params = {
|
|
238
|
+
languages: ["fr"],
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const ctx = buildCtx(params, article);
|
|
242
|
+
|
|
243
|
+
const mockFind = jest
|
|
244
|
+
.spyOn(ctx.client.articles, "find")
|
|
245
|
+
.mockImplementation(() => article);
|
|
246
|
+
|
|
247
|
+
const mockTranslate = jest
|
|
248
|
+
.spyOn(ctx.client.articles, "translate")
|
|
249
|
+
.mockImplementation(() => ({
|
|
250
|
+
article: { id: "translated-article-id" },
|
|
251
|
+
errors: null,
|
|
252
|
+
}));
|
|
253
|
+
|
|
254
|
+
const revisionErrors = [{ key: "base", message: "something went wrong" }];
|
|
255
|
+
const mockCreateRevision = jest
|
|
256
|
+
.spyOn(ctx.client.articleRevisions, "create")
|
|
257
|
+
.mockImplementation(() => ({ errors: revisionErrors }));
|
|
258
|
+
|
|
259
|
+
const mockFormatErrors = jest
|
|
260
|
+
.spyOn(ctx.client.articles, "formatErrors")
|
|
261
|
+
.mockImplementation(() => ({ base: "something went wrong" }));
|
|
262
|
+
|
|
263
|
+
const result = await TranslationsHandler.create(ctx);
|
|
264
|
+
|
|
265
|
+
expect(mockCreateRevision).toHaveBeenCalledWith(
|
|
266
|
+
"translated-article-id",
|
|
267
|
+
"fr"
|
|
268
|
+
);
|
|
269
|
+
expect(mockFormatErrors).toHaveBeenCalledWith(revisionErrors);
|
|
270
|
+
|
|
271
|
+
expect(result).toEqual({
|
|
272
|
+
assigns: {
|
|
273
|
+
resource: {
|
|
274
|
+
title: article.title,
|
|
275
|
+
tags: [],
|
|
276
|
+
visibility: "private",
|
|
277
|
+
slug: article.slug,
|
|
278
|
+
settings: article.settings,
|
|
279
|
+
widgets: article.widgets,
|
|
280
|
+
categories: ["cat1"],
|
|
281
|
+
publishAt: article.publishAt,
|
|
282
|
+
translations: {},
|
|
283
|
+
languages: ["fr"],
|
|
284
|
+
},
|
|
285
|
+
blueprint: article,
|
|
286
|
+
targetLocale: "fr",
|
|
287
|
+
errors: { resource: { base: "something went wrong" } },
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import Articles from "../../../../cloud/Articles";
|
|
2
|
+
import TranslationsHandler from "../../../../handlers/articles/TranslationsHandler";
|
|
3
|
+
import Factory from "../../../support/factory";
|
|
4
|
+
|
|
5
|
+
function buildCtx(article) {
|
|
6
|
+
return {
|
|
7
|
+
client: {
|
|
8
|
+
articles: new Articles(() => ({}), "token"),
|
|
9
|
+
},
|
|
10
|
+
params: { parent_id: article.id },
|
|
11
|
+
query: { target_locale: "" },
|
|
12
|
+
locale: "en",
|
|
13
|
+
resourceConfig: {
|
|
14
|
+
articleType: "article",
|
|
15
|
+
articleTypePlural: "articles",
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe("Article translation form", () => {
|
|
21
|
+
test("create_form when a article exists with target locale", async () => {
|
|
22
|
+
const article = Factory.build("article");
|
|
23
|
+
const ctx = buildCtx(article);
|
|
24
|
+
ctx.query.target_locale = "fr";
|
|
25
|
+
|
|
26
|
+
const mockFindArticle = jest
|
|
27
|
+
.spyOn(ctx.client.articles, "find")
|
|
28
|
+
.mockImplementation(() => article);
|
|
29
|
+
|
|
30
|
+
const result = await TranslationsHandler.create_form(ctx);
|
|
31
|
+
|
|
32
|
+
expect(mockFindArticle).toHaveBeenCalledWith(article.id, "en");
|
|
33
|
+
|
|
34
|
+
const expectedResource = structuredClone(article);
|
|
35
|
+
|
|
36
|
+
expect(result).toEqual({
|
|
37
|
+
assigns: {
|
|
38
|
+
resource: expectedResource,
|
|
39
|
+
blueprint: article,
|
|
40
|
+
targetLocale: "fr",
|
|
41
|
+
errors: {},
|
|
42
|
+
_translations: [],
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("create_form when a article exists without target locale", async () => {
|
|
48
|
+
const article = Factory.build("article");
|
|
49
|
+
const ctx = buildCtx(article);
|
|
50
|
+
|
|
51
|
+
const mockFindArticle = jest
|
|
52
|
+
.spyOn(ctx.client.articles, "find")
|
|
53
|
+
.mockImplementation(() => article);
|
|
54
|
+
|
|
55
|
+
const result = await TranslationsHandler.create_form(ctx);
|
|
56
|
+
|
|
57
|
+
expect(mockFindArticle).toHaveBeenCalledWith(article.id, "en");
|
|
58
|
+
|
|
59
|
+
const expectedResource = structuredClone(article);
|
|
60
|
+
|
|
61
|
+
expect(result).toEqual({
|
|
62
|
+
assigns: {
|
|
63
|
+
resource: expectedResource,
|
|
64
|
+
blueprint: article,
|
|
65
|
+
targetLocale: "",
|
|
66
|
+
errors: {},
|
|
67
|
+
_translations: [],
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import ArticleRevisions from "../../../../cloud/ArticleRevisions";
|
|
2
|
+
import Articles from "../../../../cloud/Articles";
|
|
3
|
+
import TranslationsHandler from "../../../../handlers/articles/TranslationsHandler";
|
|
4
|
+
import Factory from "../../../support/factory";
|
|
5
|
+
|
|
6
|
+
function buildCtx() {
|
|
7
|
+
const originalArticle = Factory.build("article", {
|
|
8
|
+
id: "original-article-id",
|
|
9
|
+
title: "Test Article Title",
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
client: {
|
|
14
|
+
articles: new Articles(() => ({}), "token"),
|
|
15
|
+
articleRevisions: new ArticleRevisions(() => ({}), "token"),
|
|
16
|
+
},
|
|
17
|
+
params: { parent_id: originalArticle.id },
|
|
18
|
+
query: { translations: "fr:fr-article-id;es:es-article-id" },
|
|
19
|
+
locale: "en",
|
|
20
|
+
resourceConfig: {
|
|
21
|
+
articleType: "article",
|
|
22
|
+
articleTypePlural: "articles",
|
|
23
|
+
},
|
|
24
|
+
originalArticle,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe("Translations Handler", () => {
|
|
29
|
+
test("index", async () => {
|
|
30
|
+
const frArticle = Factory.build("article", { id: "fr-article-id" });
|
|
31
|
+
const esArticle = Factory.build("article", { id: "es-article-id" });
|
|
32
|
+
const esRevision = Factory.build("article_revision", {
|
|
33
|
+
id: "es-revision-id",
|
|
34
|
+
});
|
|
35
|
+
const frRevision = Factory.build("article_revision", {
|
|
36
|
+
id: "fr-revision-id",
|
|
37
|
+
});
|
|
38
|
+
const ctx = buildCtx();
|
|
39
|
+
|
|
40
|
+
const mockedFind = jest
|
|
41
|
+
.spyOn(ctx.client.articles, "find")
|
|
42
|
+
.mockImplementation((id, locale) => {
|
|
43
|
+
if (locale === "fr") return frArticle;
|
|
44
|
+
if (locale === "es") return esArticle;
|
|
45
|
+
if (id === "original-article-id") return ctx.originalArticle;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const mockedRevisions = jest
|
|
49
|
+
.spyOn(ctx.client.articleRevisions, "all")
|
|
50
|
+
.mockImplementation((id, locale) => ({
|
|
51
|
+
list: locale === "fr" ? [frRevision] : [esRevision],
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
const mockedList = jest
|
|
55
|
+
.spyOn(ctx.client.articles, "list")
|
|
56
|
+
.mockImplementation(() => ({}));
|
|
57
|
+
|
|
58
|
+
const result = await TranslationsHandler.index(ctx);
|
|
59
|
+
|
|
60
|
+
expect(mockedFind).toHaveBeenCalledWith("fr-article-id", "fr");
|
|
61
|
+
expect(mockedFind).toHaveBeenCalledWith("es-article-id", "es");
|
|
62
|
+
expect(mockedRevisions).toHaveBeenCalledWith("fr-article-id", "fr");
|
|
63
|
+
expect(mockedRevisions).toHaveBeenCalledWith("es-article-id", "es");
|
|
64
|
+
expect(mockedList).toHaveBeenCalledWith(
|
|
65
|
+
{ translations: "fr:fr-article-id;es:es-article-id" },
|
|
66
|
+
"en",
|
|
67
|
+
"article"
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
expect(result).toEqual({
|
|
71
|
+
assigns: {
|
|
72
|
+
items: {},
|
|
73
|
+
visibilityOptions: ctx.client.articles.visibilityOptions(),
|
|
74
|
+
translations: [
|
|
75
|
+
{
|
|
76
|
+
...frArticle,
|
|
77
|
+
translationLocale: "fr",
|
|
78
|
+
revisionId: "fr-revision-id",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
...esArticle,
|
|
82
|
+
translationLocale: "es",
|
|
83
|
+
revisionId: "es-revision-id",
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
parent_id: "original-article-id",
|
|
87
|
+
articleTitle: "Test Article Title",
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|