@raketa-cloud/admin 0.0.34 → 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 +86 -37
  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.34",
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,30 +227,34 @@ 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, default_value: defaultValue } = 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
254
  const checkboxItem = this.tag("input", null, {
224
255
  type: "checkbox",
225
256
  value,
226
- ...opts,
257
+ ...rest,
227
258
  ...selected,
228
259
  });
229
260
 
@@ -250,6 +281,10 @@ export default class FormBuilder extends HTMLBuilder {
250
281
  return this.tag("span", children, { class: "text-red-600" });
251
282
  }
252
283
 
284
+ hint(children) {
285
+ return this.tag("span", children, { class: "help-block" });
286
+ }
287
+
253
288
  group(children, opts = {}) {
254
289
  return this.tag("div", children, {
255
290
  class: ["mb-4", opts.class].filter(Boolean).join(" "),
@@ -261,8 +296,8 @@ export default class FormBuilder extends HTMLBuilder {
261
296
  }
262
297
 
263
298
  button(name, opts) {
264
- const { label, ...rest } = opts;
265
- const defaultLabel = opts?.label || this.__getDefaultLabel(name);
299
+ const { label, hint = "" } = opts;
300
+ const defaultLabel = label || this.__getDefaultLabel(name);
266
301
  const fields = Object.keys(BUTTON_FIELDS_MAP).map((key) => {
267
302
  const innerOpts = BUTTON_FIELDS_MAP[key];
268
303
 
@@ -282,7 +317,11 @@ export default class FormBuilder extends HTMLBuilder {
282
317
 
283
318
  return this.tag(
284
319
  "fieldset",
285
- [this.tag("legend", defaultLabel, {}), ...fields].join(""),
320
+ [
321
+ this.tag("legend", defaultLabel, {}),
322
+ ...fields,
323
+ hint ? this.hint(hint) : "",
324
+ ].join(""),
286
325
  {
287
326
  class: "button-group",
288
327
  }
@@ -290,8 +329,8 @@ export default class FormBuilder extends HTMLBuilder {
290
329
  }
291
330
 
292
331
  link(name, opts) {
293
- const { label, ...rest } = opts;
294
- const defaultLabel = opts?.label || this.__getDefaultLabel(name);
332
+ const { label, hint = "" } = opts;
333
+ const defaultLabel = label || this.__getDefaultLabel(name);
295
334
  const fields = Object.keys(LINK_FIELDS_MAP).map((key) => {
296
335
  const innerOpts = LINK_FIELDS_MAP[key];
297
336
 
@@ -311,7 +350,11 @@ export default class FormBuilder extends HTMLBuilder {
311
350
 
312
351
  return this.tag(
313
352
  "fieldset",
314
- [this.tag("legend", defaultLabel, {}), ...fields].join(""),
353
+ [
354
+ this.tag("legend", defaultLabel, {}),
355
+ ...fields,
356
+ hint ? this.hint(hint) : "",
357
+ ].join(""),
315
358
  {
316
359
  class: "link-group",
317
360
  }
@@ -330,12 +373,17 @@ export default class FormBuilder extends HTMLBuilder {
330
373
  const error = this.__getError(name);
331
374
 
332
375
  if (as === "checkbox") {
376
+ const { hint = "", cbOptsRest } = rest;
333
377
  return this.group(
334
378
  [
335
379
  this.tag(
336
380
  "div",
337
381
  [
338
- this[as](name, { id: htmlID, name: toFormName(name), ...rest }),
382
+ this[as](name, {
383
+ id: htmlID,
384
+ name: toFormName(name),
385
+ ...cbOptsRest,
386
+ }),
339
387
  this.label(name, `${label || defaultLabel}`),
340
388
  ]
341
389
  .filter(Boolean)
@@ -344,6 +392,7 @@ export default class FormBuilder extends HTMLBuilder {
344
392
  class: "checkbox-wrapper",
345
393
  }
346
394
  ),
395
+ hint ? this.hint(hint) : "",
347
396
  error ? this.error(error) : "",
348
397
  ]
349
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
+ });