@raketa-cloud/admin 0.0.33 → 0.0.36

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 (43) hide show
  1. package/package.json +1 -1
  2. package/src/handlers/article_revisions/LocksHandler.js +22 -4
  3. package/src/handlers/article_revisions/UnlocksHandler.js +3 -1
  4. package/src/handlers/event_revisions/LocksHandler.js +17 -4
  5. package/src/handlers/event_revisions/UnlocksHandler.js +1 -1
  6. package/src/handlers/page_revisions/LocksHandler.js +13 -3
  7. package/src/handlers/page_revisions/UnlocksHandler.js +1 -1
  8. package/src/helpers/FormBuilder.js +108 -49
  9. package/src/helpers/Inflector.js +1 -1
  10. package/src/styles/application.css +11 -2
  11. package/src/tests/handlers/article_revisions/CodesHandler.test/update.test.js +5 -5
  12. package/src/tests/handlers/article_revisions/LocksHandler.test/create.test.js +142 -0
  13. package/src/tests/handlers/article_revisions/LocksHandler.test/create_form.test.js +55 -0
  14. package/src/tests/handlers/article_revisions/UnlocksHandler.test/create.test.js +84 -0
  15. package/src/tests/handlers/event_revisions/CodesHandler.test/update.test.js +5 -5
  16. package/src/tests/handlers/event_revisions/LocksHandler.test/create.test.js +142 -0
  17. package/src/tests/handlers/event_revisions/LocksHandler.test/create_form.test.js +55 -0
  18. package/src/tests/handlers/event_revisions/UnlocksHandler.test/create.test.js +84 -0
  19. package/src/tests/handlers/page_revisions/CodesHandler.test/edit_form.test.js +1 -1
  20. package/src/tests/handlers/page_revisions/CodesHandler.test/update.test.js +5 -5
  21. package/src/tests/handlers/page_revisions/LocksHandler.test/create.test.js +138 -0
  22. package/src/tests/handlers/page_revisions/LocksHandler.test/create_form.test.js +55 -0
  23. package/src/tests/handlers/page_revisions/UnlocksHandler.test/create.test.js +80 -0
  24. package/src/views/article_revisions/codes/edit_form.ejs +3 -3
  25. package/src/views/article_revisions/tags/edit_form.ejs +1 -1
  26. package/src/views/articles/_form.ejs +1 -1
  27. package/src/views/articles/create_form.ejs +1 -1
  28. package/src/views/articles/deletes/create_form.ejs +1 -1
  29. package/src/views/articles/translations/_form.ejs +1 -1
  30. package/src/views/articles/translations/create_form.ejs +1 -1
  31. package/src/views/event_revisions/codes/edit_form.ejs +3 -3
  32. package/src/views/event_revisions/tags/edit_form.ejs +1 -1
  33. package/src/views/events/_form.ejs +1 -1
  34. package/src/views/events/create_form.ejs +1 -1
  35. package/src/views/events/deletes/create_form.ejs +1 -1
  36. package/src/views/events/translations/_form.ejs +1 -1
  37. package/src/views/events/translations/create_form.ejs +1 -1
  38. package/src/views/page_revisions/codes/edit_form.ejs +3 -3
  39. package/src/views/pages/_form.ejs +1 -1
  40. package/src/views/pages/create_form.ejs +1 -1
  41. package/src/views/pages/deletes/create_form.ejs +1 -1
  42. package/src/views/pages/translations/_form.ejs +1 -1
  43. package/src/views/pages/translations/create_form.ejs +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raketa-cloud/admin",
3
- "version": "0.0.33",
3
+ "version": "0.0.36",
4
4
  "main": "src/index.js",
5
5
  "scripts": {
6
6
  "dev": "npx tailwindcss -i ./src/styles/application.css -o ../../apps/admin/static/application.css --watch",
@@ -6,6 +6,10 @@ const create_form = async (ctx) => {
6
6
  ctx.locale
7
7
  );
8
8
 
9
+ if (!revision) {
10
+ throw new Error(`Missing revision #${parent_id}`);
11
+ }
12
+
9
13
  const resource = {
10
14
  id: revision.id,
11
15
  originalId: revision.originalId,
@@ -25,8 +29,20 @@ const create = async (ctx) => {
25
29
  const { parent_id } = ctx.params;
26
30
  const { message } = ctx.resourceParams.resource;
27
31
 
28
- const { revision, errors, articleErrors } =
29
- await ctx.client.articleRevisions.lock(parent_id, ctx.locale, message);
32
+ const revision = await ctx.client.articleRevisions.find(
33
+ parent_id,
34
+ ctx.locale
35
+ );
36
+
37
+ if (!revision) {
38
+ throw new Error(`Missing revision #${parent_id}`);
39
+ }
40
+
41
+ const { errors, articleErrors } = await ctx.client.articleRevisions.lock(
42
+ parent_id,
43
+ ctx.locale,
44
+ message
45
+ );
30
46
 
31
47
  if (errors) {
32
48
  return {
@@ -37,7 +53,7 @@ const create = async (ctx) => {
37
53
  message,
38
54
  },
39
55
  errors: {
40
- resource: ctx.client.articles.formatErrors(errors, "message"),
56
+ resource: ctx.client.articleRevisions.formatErrors(errors, "message"),
41
57
  },
42
58
  _translations: [],
43
59
  },
@@ -45,7 +61,9 @@ const create = async (ctx) => {
45
61
  }
46
62
 
47
63
  if (articleErrors) {
48
- throw `Failed to lock revisions: ${JSON.stringify(articleErrors)}`;
64
+ throw new Error(
65
+ `Failed to lock revision: ${JSON.stringify(articleErrors)}`
66
+ );
49
67
  }
50
68
 
51
69
  return {
@@ -9,7 +9,9 @@ const create = async (ctx) => {
9
9
  }
10
10
 
11
11
  if (articleErrors) {
12
- throw `Failed to start revision: ${JSON.stringify(articleErrors)}`;
12
+ throw new Error(
13
+ `Failed to start revision: ${JSON.stringify(articleErrors)}`
14
+ );
13
15
  }
14
16
 
15
17
  return {
@@ -3,6 +3,10 @@ const create_form = async (ctx) => {
3
3
 
4
4
  const revision = await ctx.client.eventRevisions.find(parent_id, ctx.locale);
5
5
 
6
+ if (!revision) {
7
+ throw new Error(`Missing revision #${parent_id}`);
8
+ }
9
+
6
10
  const resource = {
7
11
  id: revision.id,
8
12
  originalId: revision.originalId,
@@ -22,15 +26,24 @@ const create = async (ctx) => {
22
26
  const { parent_id } = ctx.params;
23
27
  const { message } = ctx.resourceParams.resource;
24
28
 
25
- const { revision, errors, eventErrors } =
26
- await ctx.client.eventRevisions.lock(parent_id, ctx.locale, message);
29
+ const revision = await ctx.client.eventRevisions.find(parent_id, ctx.locale);
30
+
31
+ if (!revision) {
32
+ throw new Error(`Missing revision #${parent_id}`);
33
+ }
34
+
35
+ const { errors, eventErrors } = await ctx.client.eventRevisions.lock(
36
+ parent_id,
37
+ ctx.locale,
38
+ message
39
+ );
27
40
 
28
41
  if (errors) {
29
42
  return {
30
43
  assigns: {
31
44
  resource: { id: revision.id, originalId: revision.originalId, message },
32
45
  errors: {
33
- resource: ctx.client.events.formatErrors(errors, "message"),
46
+ resource: ctx.client.eventRevisions.formatErrors(errors, "message"),
34
47
  },
35
48
  _translations: [],
36
49
  },
@@ -38,7 +51,7 @@ const create = async (ctx) => {
38
51
  }
39
52
 
40
53
  if (eventErrors) {
41
- throw `Failed to lock revisions: ${JSON.stringify(eventErrors)}`;
54
+ throw new Error(`Failed to lock revision: ${JSON.stringify(eventErrors)}`);
42
55
  }
43
56
 
44
57
  return {
@@ -9,7 +9,7 @@ const create = async (ctx) => {
9
9
  }
10
10
 
11
11
  if (eventErrors) {
12
- throw `Failed to start revision: ${JSON.stringify(eventErrors)}`;
12
+ throw new Error(`Failed to start revision: ${JSON.stringify(eventErrors)}`);
13
13
  }
14
14
 
15
15
  return {
@@ -3,6 +3,10 @@ const create_form = async (ctx) => {
3
3
 
4
4
  const revision = await ctx.client.pageRevisions.find(parent_id, ctx.locale);
5
5
 
6
+ if (!revision) {
7
+ throw new Error(`Missing revision #${parent_id}`);
8
+ }
9
+
6
10
  const resource = {
7
11
  id: revision.id,
8
12
  originalId: revision.originalId,
@@ -22,7 +26,13 @@ const create = async (ctx) => {
22
26
  const { parent_id } = ctx.params;
23
27
  const { message } = ctx.resourceParams.resource;
24
28
 
25
- const { revision, errors, pageErrors } = await ctx.client.pageRevisions.lock(
29
+ const revision = await ctx.client.pageRevisions.find(parent_id, ctx.locale);
30
+
31
+ if (!revision) {
32
+ throw new Error(`Missing revision #${parent_id}`);
33
+ }
34
+
35
+ const { errors, pageErrors } = await ctx.client.pageRevisions.lock(
26
36
  parent_id,
27
37
  ctx.locale,
28
38
  message
@@ -37,7 +47,7 @@ const create = async (ctx) => {
37
47
  message,
38
48
  },
39
49
  errors: {
40
- resource: ctx.client.pages.formatErrors(errors, "message"),
50
+ resource: ctx.client.pageRevisions.formatErrors(errors, "message"),
41
51
  },
42
52
  _translations: [],
43
53
  },
@@ -45,7 +55,7 @@ const create = async (ctx) => {
45
55
  }
46
56
 
47
57
  if (pageErrors) {
48
- throw `Failed to lock revisions: ${JSON.stringify(pageErrors)}`;
58
+ throw new Error(`Failed to lock revision: ${JSON.stringify(pageErrors)}`);
49
59
  }
50
60
 
51
61
  return {
@@ -9,7 +9,7 @@ const create = async (ctx) => {
9
9
  }
10
10
 
11
11
  if (pageErrors) {
12
- throw `Failed to start revision: ${JSON.stringify(pageErrors)}`;
12
+ throw new Error(`Failed to start revision: ${JSON.stringify(pageErrors)}`);
13
13
  }
14
14
 
15
15
  return {
@@ -117,27 +117,45 @@ export default class FormBuilder extends HTMLBuilder {
117
117
  return humanize(field);
118
118
  }
119
119
 
120
+ withHint(markup, hint) {
121
+ return [markup, hint ? this.hint(hint) : ""].join("");
122
+ }
123
+
120
124
  input(name, opts = {}) {
125
+ const { hint = "", ...rest } = opts;
121
126
  const value = this.__getValue(name);
122
- return this.tag("input", null, { type: "text", name, value, ...opts });
127
+
128
+ return this.withHint(
129
+ this.tag("input", null, { type: "text", name, value, ...rest }),
130
+ hint
131
+ );
123
132
  }
124
133
 
125
134
  textarea(name, opts = {}) {
126
- const { value, ...rest } = opts;
127
-
128
- return this.tag(
129
- "textarea",
130
- value || this.__getValue(name),
131
- {
132
- name,
133
- ...rest,
134
- },
135
- { nonVoid: true }
135
+ const { value, hint = "", ...rest } = opts;
136
+
137
+ return this.withHint(
138
+ this.tag(
139
+ "textarea",
140
+ value || this.__getValue(name),
141
+ {
142
+ name,
143
+ ...rest,
144
+ },
145
+ { nonVoid: true }
146
+ ),
147
+ hint
136
148
  );
137
149
  }
138
150
 
139
151
  select(name, opts = {}) {
140
- const { value, options = [], prompt = "Please select...", ...rest } = opts;
152
+ const {
153
+ value,
154
+ options = [],
155
+ prompt = "Please select...",
156
+ hint = "",
157
+ ...rest
158
+ } = opts;
141
159
  const selected = value || this.__getValue(name);
142
160
 
143
161
  const renderedOpts = [];
@@ -154,11 +172,14 @@ export default class FormBuilder extends HTMLBuilder {
154
172
  );
155
173
  }
156
174
 
157
- return this.tag("select", renderedOpts.join(""), { name, ...rest });
175
+ return this.withHint(
176
+ this.tag("select", renderedOpts.join(""), { name, ...rest }),
177
+ hint
178
+ );
158
179
  }
159
180
 
160
181
  radio_group(name, opts = {}) {
161
- const { value, options = [], ...rest } = opts;
182
+ const { value, options = [], hint = "", ...rest } = opts;
162
183
  const selected = value || this.__getValue(name);
163
184
 
164
185
  const renderedOpts = [];
@@ -176,11 +197,17 @@ export default class FormBuilder extends HTMLBuilder {
176
197
  renderedOpts.push(this.tag("label", `${radio} ${o[1]}`));
177
198
  }
178
199
 
179
- return this.tag("div", renderedOpts.join(""), { ...rest });
200
+ return this.withHint(
201
+ this.tag("div", renderedOpts.join(""), {
202
+ ...rest,
203
+ class: ["radio-group", rest.class].filter(Boolean).join(" "),
204
+ }),
205
+ hint
206
+ );
180
207
  }
181
208
 
182
209
  checkbox_group(name, opts = {}) {
183
- const { value, options = [], ...rest } = opts;
210
+ const { value, options = [], hint = "", ...rest } = opts;
184
211
  const selected = value || this.__getValue(name);
185
212
 
186
213
  const renderedOpts = [];
@@ -200,33 +227,49 @@ export default class FormBuilder extends HTMLBuilder {
200
227
  renderedOpts.push(this.tag("label", `${checkbox} ${o[1]}`));
201
228
  }
202
229
 
203
- return this.tag(
204
- "div",
205
- renderedOpts.length
206
- ? renderedOpts.join("")
207
- : this.tag("div", "No items available.", {
208
- class: "italic opacity-50",
209
- }),
210
- {
211
- ...rest,
212
- class: ["checkbox-group", rest.class].filter(Boolean).join(" "),
213
- }
230
+ return this.withHint(
231
+ this.tag(
232
+ "div",
233
+
234
+ renderedOpts.length
235
+ ? renderedOpts.join("")
236
+ : this.tag("div", "No items available.", {
237
+ class: "italic opacity-50",
238
+ }),
239
+
240
+ {
241
+ ...rest,
242
+ class: ["checkbox-group", rest.class].filter(Boolean).join(" "),
243
+ }
244
+ ),
245
+ hint
214
246
  );
215
247
  }
216
248
 
217
249
  checkbox(name, opts = {}) {
218
- const { value: desiredValue } = opts;
219
-
250
+ const { value: desiredValue, default_value: defaultValue, ...rest } = opts;
220
251
  const value = this.__getValue(name);
221
252
  const selected = value === desiredValue ? { checked: "checked" } : {};
222
253
 
223
- return this.tag("input", null, {
254
+ const checkboxItem = this.tag("input", null, {
224
255
  type: "checkbox",
225
- name,
226
256
  value,
227
- ...opts,
257
+ ...rest,
228
258
  ...selected,
229
259
  });
260
+
261
+ if (defaultValue === undefined) {
262
+ return checkboxItem;
263
+ }
264
+
265
+ // Make sure to submit the negative value if unchecked
266
+ const hiddenComplementaryItem = this.tag("input", null, {
267
+ type: "hidden",
268
+ name: toFormName(name),
269
+ value: defaultValue,
270
+ });
271
+
272
+ return [hiddenComplementaryItem, checkboxItem].join("");
230
273
  }
231
274
 
232
275
  label(name, children, opts = {}) {
@@ -238,6 +281,10 @@ export default class FormBuilder extends HTMLBuilder {
238
281
  return this.tag("span", children, { class: "text-red-600" });
239
282
  }
240
283
 
284
+ hint(children) {
285
+ return this.tag("span", children, { class: "help-block" });
286
+ }
287
+
241
288
  group(children, opts = {}) {
242
289
  return this.tag("div", children, {
243
290
  class: ["mb-4", opts.class].filter(Boolean).join(" "),
@@ -249,8 +296,8 @@ export default class FormBuilder extends HTMLBuilder {
249
296
  }
250
297
 
251
298
  button(name, opts) {
252
- const { label, ...rest } = opts;
253
- const defaultLabel = opts?.label || this.__getDefaultLabel(name);
299
+ const { label, hint = "" } = opts;
300
+ const defaultLabel = label || this.__getDefaultLabel(name);
254
301
  const fields = Object.keys(BUTTON_FIELDS_MAP).map((key) => {
255
302
  const innerOpts = BUTTON_FIELDS_MAP[key];
256
303
 
@@ -261,17 +308,20 @@ export default class FormBuilder extends HTMLBuilder {
261
308
  label: innerOpts.label,
262
309
  class: "no-margin",
263
310
  });
264
- } else {
265
- return this.field(`${name}.${key}`, {
266
- label: innerOpts.label,
267
- class: "no-margin",
268
- });
269
311
  }
312
+ return this.field(`${name}.${key}`, {
313
+ label: innerOpts.label,
314
+ class: "no-margin",
315
+ });
270
316
  });
271
317
 
272
318
  return this.tag(
273
319
  "fieldset",
274
- [this.tag("legend", defaultLabel, {}), ...fields].join(""),
320
+ [
321
+ this.tag("legend", defaultLabel, {}),
322
+ ...fields,
323
+ hint ? this.hint(hint) : "",
324
+ ].join(""),
275
325
  {
276
326
  class: "button-group",
277
327
  }
@@ -279,8 +329,8 @@ export default class FormBuilder extends HTMLBuilder {
279
329
  }
280
330
 
281
331
  link(name, opts) {
282
- const { label, ...rest } = opts;
283
- const defaultLabel = opts?.label || this.__getDefaultLabel(name);
332
+ const { label, hint = "" } = opts;
333
+ const defaultLabel = label || this.__getDefaultLabel(name);
284
334
  const fields = Object.keys(LINK_FIELDS_MAP).map((key) => {
285
335
  const innerOpts = LINK_FIELDS_MAP[key];
286
336
 
@@ -291,17 +341,20 @@ export default class FormBuilder extends HTMLBuilder {
291
341
  label: innerOpts.label,
292
342
  class: "no-margin",
293
343
  });
294
- } else {
295
- return this.field(`${name}.${key}`, {
296
- label: innerOpts.label,
297
- class: "no-margin",
298
- });
299
344
  }
345
+ return this.field(`${name}.${key}`, {
346
+ label: innerOpts.label,
347
+ class: "no-margin",
348
+ });
300
349
  });
301
350
 
302
351
  return this.tag(
303
352
  "fieldset",
304
- [this.tag("legend", defaultLabel, {}), ...fields].join(""),
353
+ [
354
+ this.tag("legend", defaultLabel, {}),
355
+ ...fields,
356
+ hint ? this.hint(hint) : "",
357
+ ].join(""),
305
358
  {
306
359
  class: "link-group",
307
360
  }
@@ -320,12 +373,17 @@ export default class FormBuilder extends HTMLBuilder {
320
373
  const error = this.__getError(name);
321
374
 
322
375
  if (as === "checkbox") {
376
+ const { hint = "", cbOptsRest } = rest;
323
377
  return this.group(
324
378
  [
325
379
  this.tag(
326
380
  "div",
327
381
  [
328
- this[as](name, { id: htmlID, name: toFormName(name), ...rest }),
382
+ this[as](name, {
383
+ id: htmlID,
384
+ name: toFormName(name),
385
+ ...cbOptsRest,
386
+ }),
329
387
  this.label(name, `${label || defaultLabel}`),
330
388
  ]
331
389
  .filter(Boolean)
@@ -334,6 +392,7 @@ export default class FormBuilder extends HTMLBuilder {
334
392
  class: "checkbox-wrapper",
335
393
  }
336
394
  ),
395
+ hint ? this.hint(hint) : "",
337
396
  error ? this.error(error) : "",
338
397
  ]
339
398
  .filter(Boolean)
@@ -1,6 +1,6 @@
1
1
  const Inflector = {
2
2
  capitalize: (str) => str[0].toUpperCase() + str.slice(1),
3
+ downcase: (str) => str.toLowerCase(),
3
4
  };
4
5
 
5
6
  export default Inflector;
6
-
@@ -26,7 +26,6 @@ body {
26
26
  }
27
27
 
28
28
  @layer components {
29
-
30
29
  /* Utils */
31
30
  .dbg {
32
31
  @apply outline-1 outline-dashed outline-red-500;
@@ -144,10 +143,16 @@ body {
144
143
  @apply border-red-600;
145
144
  }
146
145
 
146
+ .help-block {
147
+ @apply opacity-50 text-sm;
148
+ }
149
+
150
+ .radio-group,
147
151
  .checkbox-group {
148
152
  @apply max-h-[200px] overflow-y-auto py-1 px-2 mt-1 rounded shadow-inner border border-neutral-300 bg-raketa-background dark:border-neutral-700;
149
153
  }
150
154
 
155
+ .radio-group label,
151
156
  .checkbox-group label {
152
157
  @apply flex gap-2 text-nowrap items-center py-2;
153
158
  }
@@ -156,6 +161,10 @@ body {
156
161
  @apply size-4 px-0;
157
162
  }
158
163
 
164
+ .radio-group input[type="radio"] {
165
+ @apply size-4 px-0 rounded-full;
166
+ }
167
+
159
168
  .checkbox-wrapper {
160
169
  @apply flex gap-2 text-nowrap;
161
170
  }
@@ -275,7 +284,7 @@ body {
275
284
  .pagination {
276
285
  @apply text-center py-8 text-raketa-foreground/50;
277
286
 
278
- &>div {
287
+ & > div {
279
288
  @apply inline-flex items-center gap-4;
280
289
  }
281
290
 
@@ -30,7 +30,7 @@ describe("Article revision code settings", () => {
30
30
  settings: {
31
31
  codes: {
32
32
  scripts: JSON.stringify([{ type: "bla" }]),
33
- structured_data: JSON.stringify({ type: "article" }),
33
+ structured_json: JSON.stringify({ type: "article" }),
34
34
  },
35
35
  },
36
36
  };
@@ -63,7 +63,7 @@ describe("Article revision code settings", () => {
63
63
  ...revision.settings,
64
64
  codes: {
65
65
  scripts: [{ type: "bla" }],
66
- structured_data: { type: "article" },
66
+ structured_json: { type: "article" },
67
67
  },
68
68
  },
69
69
  });
@@ -82,7 +82,7 @@ describe("Article revision code settings", () => {
82
82
  settings: {
83
83
  codes: {
84
84
  scripts: JSON.stringify([{ type: "bla" }]),
85
- structured_data: JSON.stringify({ type: "article" }),
85
+ structured_json: JSON.stringify({ type: "article" }),
86
86
  },
87
87
  },
88
88
  };
@@ -115,7 +115,7 @@ describe("Article revision code settings", () => {
115
115
  ...revision.settings,
116
116
  codes: {
117
117
  scripts: [{ type: "bla" }],
118
- structured_data: { type: "article" },
118
+ structured_json: { type: "article" },
119
119
  },
120
120
  },
121
121
  });
@@ -129,7 +129,7 @@ describe("Article revision code settings", () => {
129
129
  ...revision.settings,
130
130
  codes: {
131
131
  scripts: [{ type: "bla" }],
132
- structured_data: { type: "article" },
132
+ structured_json: { type: "article" },
133
133
  },
134
134
  },
135
135
  },
@@ -0,0 +1,142 @@
1
+ import Factory from "../../../support/factory";
2
+ import ArticleRevisions from "../../../../cloud/ArticleRevisions";
3
+ import LocksHandler from "../../../../handlers/article_revisions/LocksHandler";
4
+
5
+ function buildCtx(revision, lockSettings) {
6
+ return {
7
+ client: {
8
+ articleRevisions: new ArticleRevisions(() => ({}), "token"),
9
+ },
10
+ params: { parent_id: revision.id },
11
+ resourceParams: {
12
+ resource: lockSettings,
13
+ },
14
+ locale: "en",
15
+ resourceConfig: {
16
+ articleType: "article",
17
+ articleTypePlural: "articles",
18
+ },
19
+ };
20
+ }
21
+
22
+ describe("Article revision lock ", () => {
23
+ test("lock successful", async () => {
24
+ const revision = Factory.build("article_revision");
25
+ const lockSettings = { message: "Some lock message" };
26
+ const ctx = buildCtx(revision, lockSettings);
27
+
28
+ const mockFind = jest
29
+ .spyOn(ctx.client.articleRevisions, "find")
30
+ .mockImplementation(() => revision);
31
+
32
+ const mockLock = jest
33
+ .spyOn(ctx.client.articleRevisions, "lock")
34
+ .mockImplementation(() => ({
35
+ revision,
36
+ errors: null,
37
+ articleErrors: null,
38
+ }));
39
+
40
+ const result = await LocksHandler.create(ctx);
41
+
42
+ expect(mockFind).toHaveBeenCalledWith(revision.id, "en");
43
+ expect(mockLock).toHaveBeenCalledWith(
44
+ revision.id,
45
+ "en",
46
+ lockSettings.message
47
+ );
48
+
49
+ expect(result).toEqual({
50
+ redirect: `/en/articles/${revision.originalId}`,
51
+ });
52
+ });
53
+
54
+ test("lock failed with errors", async () => {
55
+ const revision = Factory.build("article_revision");
56
+ const lockSettings = { message: "" };
57
+ const ctx = buildCtx(revision, lockSettings);
58
+ const errors = [{ key: "message", message: "Cannot be blank" }];
59
+
60
+ const mockFind = jest
61
+ .spyOn(ctx.client.articleRevisions, "find")
62
+ .mockImplementation(() => revision);
63
+
64
+ const mockLock = jest
65
+ .spyOn(ctx.client.articleRevisions, "lock")
66
+ .mockImplementation(() => ({
67
+ revision: null,
68
+ errors,
69
+ articleErrors: null,
70
+ }));
71
+
72
+ const result = await LocksHandler.create(ctx);
73
+
74
+ expect(mockFind).toHaveBeenCalledWith(revision.id, "en");
75
+ expect(mockLock).toHaveBeenCalledWith(
76
+ revision.id,
77
+ "en",
78
+ lockSettings.message
79
+ );
80
+
81
+ expect(result).toEqual({
82
+ assigns: {
83
+ resource: {
84
+ id: revision.id,
85
+ originalId: revision.originalId,
86
+ message: lockSettings.message,
87
+ },
88
+ errors: {
89
+ resource: { message: "Cannot be blank" },
90
+ },
91
+ _translations: [],
92
+ },
93
+ });
94
+ });
95
+
96
+ test("lock failed with article errors", async () => {
97
+ const revision = Factory.build("article_revision");
98
+ const lockSettings = { message: "Some lock message" };
99
+ const ctx = buildCtx(revision, lockSettings);
100
+ const articleErrors = [
101
+ { key: "generalError", message: "Failed to lock revision" },
102
+ ];
103
+
104
+ const mockFind = jest
105
+ .spyOn(ctx.client.articleRevisions, "find")
106
+ .mockImplementation(() => revision);
107
+
108
+ const mockLock = jest
109
+ .spyOn(ctx.client.articleRevisions, "lock")
110
+ .mockImplementation(() => ({
111
+ revision: null,
112
+ errors: null,
113
+ articleErrors,
114
+ }));
115
+
116
+ await expect(LocksHandler.create(ctx)).rejects.toThrow(
117
+ `Failed to lock revision: ${JSON.stringify(articleErrors)}`
118
+ );
119
+
120
+ expect(mockFind).toHaveBeenCalledWith(revision.id, "en");
121
+ expect(mockLock).toHaveBeenCalledWith(
122
+ revision.id,
123
+ "en",
124
+ lockSettings.message
125
+ );
126
+ });
127
+
128
+ test("create for non existing article revision", async () => {
129
+ const lockSettings = { message: "Some lock message" };
130
+ const ctx = buildCtx({ id: 42 }, lockSettings);
131
+
132
+ const mockFind = jest
133
+ .spyOn(ctx.client.articleRevisions, "find")
134
+ .mockImplementation(() => null);
135
+
136
+ await expect(LocksHandler.create(ctx)).rejects.toThrow(
137
+ `Missing revision #42`
138
+ );
139
+
140
+ expect(mockFind).toHaveBeenCalledWith(42, "en");
141
+ });
142
+ });