@raketa-cloud/admin 1.8.18 → 1.8.19

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 (54) hide show
  1. package/dist/application.css +288 -11
  2. package/package.json +8 -6
  3. package/src/handlers/resources/ReportsAPIHandler.js +27 -0
  4. package/src/handlers/resources/ResourcesHandler.js +16 -0
  5. package/src/helpers/reports/cwv.js +217 -0
  6. package/src/helpers/reports/googleAuth.js +149 -0
  7. package/src/helpers/reports/quick-insights.js +294 -0
  8. package/src/helpers/reports/reportsUtils.js +181 -0
  9. package/src/index.js +8 -0
  10. package/src/localCache.js +112 -0
  11. package/src/report/analytics.js +107 -0
  12. package/src/report/reports.js +390 -0
  13. package/src/routeAssets.js +74 -0
  14. package/src/routeResourceModule.js +24 -0
  15. package/src/routeStandard.js +7 -1
  16. package/src/styles/application.css +1 -1
  17. package/src/tests/handlers/RedirectsHandler.test/notes.test.js +56 -0
  18. package/src/tests/handlers/_shared/SettingsHandler.test/global_variables.test.js +54 -0
  19. package/src/tests/handlers/categories/update_defaults.test.js +35 -0
  20. package/src/tests/handlers/content_tasks/ContentTasksHandler.test/editors.test.js +29 -0
  21. package/src/tests/handlers/resource_revisions/PublishesHandler.test/schedule.test.js +137 -0
  22. package/src/tests/handlers/resource_revisions/TagHandler.test/default_locale_tags.test.js +162 -0
  23. package/src/tests/handlers/resource_revisions/WidgetHandler.test/defaults.test.js +61 -0
  24. package/src/tests/handlers/resources/DeletesHandler.test/no_redirect.test.js +55 -0
  25. package/src/tests/handlers/resources/ReportsAPIHandler.test/show.test.js +156 -0
  26. package/src/tests/handlers/resources/ResourcesHandler.test/show.test.js +8 -0
  27. package/src/tests/handlers/resources/ResourcesHandler.test/show_reports.test.js +94 -0
  28. package/src/tests/handlers/resources/TranslationsHandler.test/edge_cases.test.js +211 -0
  29. package/src/tests/helpers/CSVExportHelper.branches.test.js +57 -0
  30. package/src/tests/helpers/FormBuilder.branches.test.js +151 -0
  31. package/src/tests/helpers/HTMLBuilder.test.js +51 -0
  32. package/src/tests/helpers/historyUtils.branches.test.js +36 -0
  33. package/src/tests/helpers/reports/cwv.test.js +239 -0
  34. package/src/tests/helpers/reports/quick-insights.test.js +593 -0
  35. package/src/tests/helpers/reports/reportsUtils.test.js +425 -0
  36. package/src/tests/helpers/utils.test.js +37 -0
  37. package/src/tests/helpers/view_helpers.test.js +87 -0
  38. package/src/tests/index.test.js +74 -0
  39. package/src/tests/localCache.test.js +236 -0
  40. package/src/tests/report/analytics.test.js +230 -0
  41. package/src/tests/report/reports.test.js +831 -0
  42. package/src/tests/routeAssets.test.js +120 -0
  43. package/src/tests/routeResourceModule.test.js +99 -2
  44. package/src/tests/routeStandard.test.js +3 -3
  45. package/src/tests/support/fakeDom.js +164 -0
  46. package/src/views/resources/_shared/_core_web_vitals.ejs +27 -0
  47. package/src/views/resources/_shared/_quick_insights.ejs +64 -0
  48. package/src/views/resources/_shared/_scheduled_revisions.ejs +88 -0
  49. package/src/views/resources/_shared/_title.ejs +16 -12
  50. package/src/views/resources/_shared/_toolbar.ejs +11 -21
  51. package/src/views/resources/_shared/_traffic_chart.ejs +27 -0
  52. package/src/views/resources/_shared/_translations_strip.ejs +13 -25
  53. package/src/views/resources/_shared/_vital_meter.ejs +54 -0
  54. package/src/views/resources/show.ejs +88 -98
@@ -0,0 +1,425 @@
1
+ import {
2
+ reportsAvailability,
3
+ resourceReports,
4
+ } from "../../../helpers/reports/reportsUtils";
5
+
6
+ import {
7
+ cruxApiKeyConfigured,
8
+ pageWebVitals,
9
+ } from "../../../helpers/reports/cwv";
10
+ import { serviceAccountConfigured } from "../../../helpers/reports/googleAuth";
11
+ import { pageInsights } from "../../../helpers/reports/quick-insights";
12
+
13
+ jest.mock("../../../helpers/reports/cwv", () => ({
14
+ cruxApiKeyConfigured: jest.fn(),
15
+ pageWebVitals: jest.fn(),
16
+ }));
17
+
18
+ jest.mock("../../../helpers/reports/googleAuth", () => ({
19
+ serviceAccountConfigured: jest.fn(),
20
+ }));
21
+
22
+ jest.mock("../../../helpers/reports/quick-insights", () => ({
23
+ pageInsights: jest.fn(),
24
+ }));
25
+
26
+ const RESOURCE = { id: "42", slug: "some-page" };
27
+
28
+ function buildCtx({ ga = {}, reports = true, changes = { list: [] } } = {}) {
29
+ return {
30
+ locale: "en",
31
+ // Publish markers are read off the resource's own change log.
32
+ client: {
33
+ resourceChanges: {
34
+ list: jest.fn(() =>
35
+ changes instanceof Error
36
+ ? Promise.reject(changes)
37
+ : Promise.resolve(changes),
38
+ ),
39
+ },
40
+ },
41
+ resourceConfig: {
42
+ resourceType: "page",
43
+ resourceTypePlural: "pages",
44
+ reports,
45
+ },
46
+ globalConfig: { ga },
47
+ remoteConfig: {
48
+ resourcePath: jest.fn((resource) => `/${resource.slug}`),
49
+ resourceUrl: jest.fn(
50
+ (resource) => `https://www.example.com/${resource.slug}`,
51
+ ),
52
+ },
53
+ };
54
+ }
55
+
56
+ // GA needs a property plus a service account, CrUX needs an API key.
57
+ function enableGa() {
58
+ serviceAccountConfigured.mockReturnValue(true);
59
+ }
60
+
61
+ function enableCwv() {
62
+ cruxApiKeyConfigured.mockReturnValue(true);
63
+ }
64
+
65
+ const INSIGHTS = {
66
+ summary: { views: { value: 10 } },
67
+ dateRange: { startDate: "2026-07-01", endDate: "2026-07-28" },
68
+ rows: [
69
+ { date: "2026-07-01", views: 4 },
70
+ { date: "2026-07-02", views: 6 },
71
+ ],
72
+ };
73
+
74
+ const WEB_VITALS = { assessment: "good", metrics: [] };
75
+
76
+ describe("reportsUtils", () => {
77
+ beforeEach(() => {
78
+ serviceAccountConfigured.mockReturnValue(false);
79
+ cruxApiKeyConfigured.mockReturnValue(false);
80
+ pageInsights.mockResolvedValue(INSIGHTS);
81
+ pageWebVitals.mockResolvedValue(WEB_VITALS);
82
+ });
83
+
84
+ afterEach(() => {
85
+ jest.clearAllMocks();
86
+ });
87
+
88
+ describe("reportsAvailability", () => {
89
+ test("reports each side independently", () => {
90
+ enableCwv();
91
+
92
+ expect(reportsAvailability(buildCtx())).toEqual({
93
+ insights: false,
94
+ webVitals: true,
95
+ });
96
+ });
97
+
98
+ test("reports both when everything is configured", () => {
99
+ enableGa();
100
+ enableCwv();
101
+
102
+ expect(
103
+ reportsAvailability(buildCtx({ ga: { propertyId: "123" } })),
104
+ ).toEqual({ insights: true, webVitals: true });
105
+ expect(serviceAccountConfigured).toHaveBeenCalledWith({
106
+ propertyId: "123",
107
+ });
108
+ });
109
+
110
+ test("skips insights when the property id is missing", () => {
111
+ enableGa();
112
+
113
+ expect(reportsAvailability(buildCtx()).insights).toBe(false);
114
+ });
115
+
116
+ test("skips insights when the service account is missing", () => {
117
+ expect(
118
+ reportsAvailability(buildCtx({ ga: { propertyId: "123" } })).insights,
119
+ ).toBe(false);
120
+ });
121
+
122
+ test("hides both sides when the resource has reports switched off", () => {
123
+ enableGa();
124
+ enableCwv();
125
+ const ctx = buildCtx({ ga: { propertyId: "123" }, reports: false });
126
+
127
+ expect(reportsAvailability(ctx)).toEqual({
128
+ insights: false,
129
+ webVitals: false,
130
+ });
131
+ });
132
+ });
133
+
134
+ describe("resourceReports", () => {
135
+ test("returns null for a side that is not configured", async () => {
136
+ expect(await resourceReports(buildCtx(), RESOURCE, "page")).toEqual({
137
+ insights: null,
138
+ webVitals: null,
139
+ });
140
+
141
+ expect(pageInsights).not.toHaveBeenCalled();
142
+ expect(pageWebVitals).not.toHaveBeenCalled();
143
+ });
144
+
145
+ test("fetches insights with the resource path and builds the chart series", async () => {
146
+ enableGa();
147
+ const ctx = buildCtx({ ga: { propertyId: "123" } });
148
+
149
+ const { insights } = await resourceReports(ctx, RESOURCE, "page");
150
+
151
+ expect(pageInsights).toHaveBeenCalledWith(
152
+ "/some-page",
153
+ { propertyId: "123" },
154
+ { cacheKey: "page:42" },
155
+ );
156
+ expect(ctx.remoteConfig.resourcePath).toHaveBeenCalledWith(
157
+ RESOURCE,
158
+ "page",
159
+ "en",
160
+ );
161
+ expect(insights).toEqual({
162
+ pagePath: "/some-page",
163
+ summary: INSIGHTS.summary,
164
+ dateRange: INSIGHTS.dateRange,
165
+ series: {
166
+ dates: ["2026-07-01", "2026-07-02"],
167
+ labels: ["July 1", "July 2"],
168
+ data: [4, 6],
169
+ },
170
+ publishDates: [],
171
+ error: null,
172
+ });
173
+ });
174
+
175
+ test("marks the days a revision was published", async () => {
176
+ enableGa();
177
+ const ctx = buildCtx({
178
+ ga: { propertyId: "123" },
179
+ changes: {
180
+ list: [
181
+ {
182
+ changeType: "revision_publish",
183
+ insertedAt: "2026-07-02T09:12:33Z",
184
+ },
185
+ // Same day twice, and a publish outside the plotted window.
186
+ {
187
+ changeType: "revision_publish",
188
+ insertedAt: "2026-07-02T18:40:00Z",
189
+ },
190
+ {
191
+ changeType: "revision_publish",
192
+ insertedAt: "2026-06-11T08:00:00Z",
193
+ },
194
+ ],
195
+ },
196
+ });
197
+
198
+ const { insights } = await resourceReports(ctx, RESOURCE, "page");
199
+
200
+ expect(ctx.client.resourceChanges.list).toHaveBeenCalledWith(
201
+ { "f:change_type": ["revision_publish"] },
202
+ "42",
203
+ "en",
204
+ "page",
205
+ );
206
+ expect(insights.publishDates).toEqual(["2026-07-02"]);
207
+ });
208
+
209
+ test("reads the day off a timestamp that carries no zone", async () => {
210
+ enableGa();
211
+ const ctx = buildCtx({
212
+ ga: { propertyId: "123" },
213
+ changes: { list: [{ insertedAt: "2026-07-01T23:45:00" }] },
214
+ });
215
+
216
+ const { insights } = await resourceReports(ctx, RESOURCE, "page");
217
+
218
+ expect(insights.publishDates).toEqual(["2026-07-01"]);
219
+ });
220
+
221
+ test("drops changes with an unusable timestamp", async () => {
222
+ enableGa();
223
+ const ctx = buildCtx({
224
+ ga: { propertyId: "123" },
225
+ changes: { list: [{ insertedAt: "whenever" }, { insertedAt: null }] },
226
+ });
227
+
228
+ const { insights } = await resourceReports(ctx, RESOURCE, "page");
229
+
230
+ expect(insights.publishDates).toEqual([]);
231
+ });
232
+
233
+ test("still reports when the change log fails", async () => {
234
+ enableGa();
235
+ const ctx = buildCtx({
236
+ ga: { propertyId: "123" },
237
+ changes: new Error("history unavailable"),
238
+ });
239
+
240
+ const { insights } = await resourceReports(ctx, RESOURCE, "page");
241
+
242
+ expect(insights.error).toBeNull();
243
+ expect(insights.publishDates).toEqual([]);
244
+ });
245
+
246
+ test("asks GA about the site root for the homepage", async () => {
247
+ enableGa();
248
+ const ctx = buildCtx({ ga: { propertyId: "123" } });
249
+
250
+ await resourceReports(ctx, { id: "1", slug: "homepage" }, "page");
251
+
252
+ expect(pageInsights).toHaveBeenCalledWith(
253
+ "/",
254
+ expect.anything(),
255
+ expect.anything(),
256
+ );
257
+ expect(ctx.remoteConfig.resourcePath).not.toHaveBeenCalled();
258
+ });
259
+
260
+ test("honours a project specific homepage slug", async () => {
261
+ enableGa();
262
+ const ctx = buildCtx({
263
+ ga: { propertyId: "123", homepageSlug: "start" },
264
+ });
265
+
266
+ await resourceReports(ctx, { id: "1", slug: "start" }, "page");
267
+
268
+ expect(pageInsights).toHaveBeenCalledWith(
269
+ "/",
270
+ expect.anything(),
271
+ expect.anything(),
272
+ );
273
+ });
274
+
275
+ test("copes with an insights payload that has no rows", async () => {
276
+ enableGa();
277
+ pageInsights.mockResolvedValue({ summary: {}, dateRange: {} });
278
+
279
+ const { insights } = await resourceReports(
280
+ buildCtx({ ga: { propertyId: "123" } }),
281
+ RESOURCE,
282
+ "page",
283
+ );
284
+
285
+ expect(insights.series).toEqual({ dates: [], labels: [], data: [] });
286
+ });
287
+
288
+ test("returns an error when GA has no data", async () => {
289
+ enableGa();
290
+ pageInsights.mockResolvedValue(null);
291
+
292
+ const { insights } = await resourceReports(
293
+ buildCtx({ ga: { propertyId: "123" } }),
294
+ RESOURCE,
295
+ "page",
296
+ );
297
+
298
+ expect(insights).toEqual({
299
+ error: "No analytics data (check GA config / server logs)",
300
+ });
301
+ });
302
+
303
+ test("returns the error message when GA throws", async () => {
304
+ enableGa();
305
+ pageInsights.mockRejectedValue(new Error("GA exploded"));
306
+
307
+ const { insights } = await resourceReports(
308
+ buildCtx({ ga: { propertyId: "123" } }),
309
+ RESOURCE,
310
+ "page",
311
+ );
312
+
313
+ expect(insights).toEqual({ error: "GA exploded" });
314
+ });
315
+
316
+ test("stringifies a non-error GA rejection", async () => {
317
+ enableGa();
318
+ pageInsights.mockRejectedValue("plain string");
319
+
320
+ const { insights } = await resourceReports(
321
+ buildCtx({ ga: { propertyId: "123" } }),
322
+ RESOURCE,
323
+ "page",
324
+ );
325
+
326
+ expect(insights).toEqual({ error: "plain string" });
327
+ });
328
+
329
+ test("builds the CrUX url from the configured public url", async () => {
330
+ enableCwv();
331
+ const ctx = buildCtx({ ga: { publicUrl: "https://www.example.com" } });
332
+
333
+ const { webVitals } = await resourceReports(ctx, RESOURCE, "page");
334
+
335
+ expect(pageWebVitals).toHaveBeenCalledWith(
336
+ "https://www.example.com/some-page",
337
+ ctx.globalConfig.ga,
338
+ { cacheKey: "page:42" },
339
+ );
340
+ expect(webVitals).toEqual({
341
+ pageUrl: "https://www.example.com/some-page",
342
+ ...WEB_VITALS,
343
+ error: null,
344
+ });
345
+ });
346
+
347
+ test("asks CrUX about the origin root for the homepage", async () => {
348
+ enableCwv();
349
+ const ctx = buildCtx({ ga: { publicUrl: "https://www.example.com" } });
350
+
351
+ await resourceReports(ctx, { id: "1", slug: "homepage" }, "page");
352
+
353
+ expect(pageWebVitals).toHaveBeenCalledWith(
354
+ "https://www.example.com/",
355
+ expect.anything(),
356
+ expect.anything(),
357
+ );
358
+ });
359
+
360
+ test("falls back to the resource url without a public url", async () => {
361
+ enableCwv();
362
+ const ctx = buildCtx();
363
+
364
+ await resourceReports(ctx, RESOURCE, "page");
365
+
366
+ expect(ctx.remoteConfig.resourceUrl).toHaveBeenCalledWith(
367
+ RESOURCE,
368
+ "page",
369
+ "en",
370
+ );
371
+ expect(pageWebVitals).toHaveBeenCalledWith(
372
+ "https://www.example.com/some-page",
373
+ expect.anything(),
374
+ expect.anything(),
375
+ );
376
+ });
377
+
378
+ test("collapses the homepage resource url down to the origin", async () => {
379
+ enableCwv();
380
+ const ctx = buildCtx();
381
+ ctx.remoteConfig.resourceUrl.mockReturnValue(
382
+ "https://www.example.com/homepage?preview=1#top",
383
+ );
384
+
385
+ await resourceReports(ctx, { id: "1", slug: "homepage" }, "page");
386
+
387
+ expect(pageWebVitals).toHaveBeenCalledWith(
388
+ "https://www.example.com/",
389
+ expect.anything(),
390
+ expect.anything(),
391
+ );
392
+ });
393
+
394
+ test("returns an error when CrUX has no data", async () => {
395
+ enableCwv();
396
+ pageWebVitals.mockResolvedValue(null);
397
+
398
+ const { webVitals } = await resourceReports(buildCtx(), RESOURCE, "page");
399
+
400
+ expect(webVitals).toEqual({
401
+ error: "No Core Web Vitals data (check server logs)",
402
+ });
403
+ });
404
+
405
+ test("returns the error message when CrUX throws", async () => {
406
+ enableCwv();
407
+ pageWebVitals.mockRejectedValue(new Error("CrUX exploded"));
408
+
409
+ const { webVitals } = await resourceReports(buildCtx(), RESOURCE, "page");
410
+
411
+ expect(webVitals).toEqual({ error: "CrUX exploded" });
412
+ });
413
+
414
+ test("fetches both sides together when both are configured", async () => {
415
+ enableGa();
416
+ enableCwv();
417
+ const ctx = buildCtx({ ga: { propertyId: "123" } });
418
+
419
+ const result = await resourceReports(ctx, RESOURCE, "page");
420
+
421
+ expect(result.insights.error).toBeNull();
422
+ expect(result.webVitals.error).toBeNull();
423
+ });
424
+ });
425
+ });
@@ -24,6 +24,43 @@ describe("includedInCollection", () => {
24
24
  it("returns false when the given collection undefined", () => {
25
25
  expect(utils.includedInCollection("key", undefined)).toEqual(false);
26
26
  });
27
+
28
+ it("throws when the given collection is neither a string nor an array", () => {
29
+ expect(() => utils.includedInCollection("key", { key: true })).toThrow(
30
+ "Invalid input for collection. Expected a string or an array.",
31
+ );
32
+ });
33
+ });
34
+
35
+ describe("formattedDateTime", () => {
36
+ it("formats an ISO timestamp as a short date and time", () => {
37
+ expect(utils.formattedDateTime("2026-08-30T19:05:00Z")).toEqual(
38
+ new Date("2026-08-30T19:05:00Z").toLocaleString("en-GB", {
39
+ dateStyle: "short",
40
+ timeStyle: "short",
41
+ }),
42
+ );
43
+ });
44
+
45
+ it("returns an invalid marker for an unparsable value", () => {
46
+ expect(utils.formattedDateTime("not a date")).toEqual("Invalid Date");
47
+ });
48
+ });
49
+
50
+ describe("formattedDate", () => {
51
+ it("formats an ISO timestamp as a short date", () => {
52
+ expect(utils.formattedDate("2026-08-30T19:05:00Z")).toEqual(
53
+ new Date("2026-08-30T19:05:00Z").toLocaleString("en-GB", {
54
+ dateStyle: "short",
55
+ }),
56
+ );
57
+ });
58
+
59
+ it("returns an empty string for a missing value", () => {
60
+ expect(utils.formattedDate(null)).toEqual("");
61
+ expect(utils.formattedDate(undefined)).toEqual("");
62
+ expect(utils.formattedDate("")).toEqual("");
63
+ });
27
64
  });
28
65
 
29
66
  describe("feature", () => {
@@ -0,0 +1,87 @@
1
+ import form_for from "../../helpers/form_for";
2
+ import pagination_helper from "../../helpers/pagination_helper";
3
+ import sort_button from "../../helpers/sort_button";
4
+
5
+ // The thin wrappers handed to the EJS views — each builds its helper and
6
+ // returns rendered markup.
7
+ describe("form_for", () => {
8
+ test("yields a form builder bound to the object", () => {
9
+ const helper = form_for();
10
+
11
+ const result = helper({ resource: { title: "Hello" } }, {}, (form) =>
12
+ form.field("resource.title"),
13
+ );
14
+
15
+ expect(result).toContain('name="resource[title]"');
16
+ expect(result).toContain('value="Hello"');
17
+ expect(result).toContain("Title");
18
+ });
19
+
20
+ test("passes the errors through to the builder", () => {
21
+ const helper = form_for();
22
+
23
+ const result = helper(
24
+ { resource: { title: "" } },
25
+ { resource: { title: "Can't be blank" } },
26
+ (form) => form.field("resource.title"),
27
+ );
28
+
29
+ expect(result).toContain("has-error");
30
+ expect(result).toContain("Can't be blank");
31
+ });
32
+
33
+ test("hands the view helpers config to the builder", () => {
34
+ const config = { imagePickerUrl: "/en/images" };
35
+ const helper = form_for(config);
36
+
37
+ const result = helper({}, {}, (form) => form.config);
38
+
39
+ expect(result).toEqual(config);
40
+ });
41
+
42
+ test("defaults every argument", () => {
43
+ expect(form_for()()).toBeUndefined();
44
+ });
45
+ });
46
+
47
+ describe("pagination_helper", () => {
48
+ const router = { path: "/en/pages", query: { q: "news" } };
49
+
50
+ test("renders the pagination element", () => {
51
+ const result = pagination_helper({ currentPage: 2, pagesCount: 5 }, router);
52
+
53
+ expect(result).toContain('class="pagination"');
54
+ expect(result).toContain("of 5");
55
+ expect(result).toContain("q=news&page=1");
56
+ expect(result).toContain("q=news&page=3");
57
+ });
58
+
59
+ test("renders nothing for a single page", () => {
60
+ expect(
61
+ pagination_helper({ currentPage: 1, pagesCount: 1 }, router),
62
+ ).toEqual("");
63
+ });
64
+ });
65
+
66
+ describe("sort_button", () => {
67
+ test("renders an unsorted button", () => {
68
+ const result = sort_button("Title", "title", {
69
+ path: "/en/pages",
70
+ query: {},
71
+ });
72
+
73
+ expect(result).toContain("Title");
74
+ expect(result).toContain("order_direction=ASC&order_by=title");
75
+ expect(result).toContain('class="btn-sort"');
76
+ });
77
+
78
+ test("flips the direction of the sorted column", () => {
79
+ const result = sort_button("Title", "title", {
80
+ path: "/en/pages",
81
+ query: { order_by: "title", order_direction: "ASC" },
82
+ });
83
+
84
+ expect(result).toContain("order_direction=DESC");
85
+ expect(result).toContain("btn-sort is-active");
86
+ });
87
+ });
@@ -0,0 +1,74 @@
1
+ import * as adminPackage from "../index";
2
+
3
+ // The barrel is the package's public surface — a missing or renamed file here
4
+ // breaks consumers at import time rather than at first use.
5
+ describe("@raketa-cloud/admin entry point", () => {
6
+ const EXPECTED_EXPORTS = [
7
+ "HonoAdapter",
8
+ "routeCollection",
9
+ "routeNestedCollection",
10
+ "routeMember",
11
+ "routeNestedMember",
12
+ "setGlobalContext",
13
+ "routeResourceModule",
14
+ "routeSettings",
15
+ "setupCloud",
16
+ "routeStandard",
17
+ "routeCategoriesModule",
18
+ "buildResourcePath",
19
+ "setBeforeFilter",
20
+ "setAfterFilter",
21
+ "RaketaClient",
22
+ "Redirects",
23
+ "Tags",
24
+ "Settings",
25
+ "ResourceChanges",
26
+ "ResourceRevisions",
27
+ "Resources",
28
+ "Categories",
29
+ "localCache",
30
+ "pageInsights",
31
+ "runPageReport",
32
+ "runSummary",
33
+ "pageWebVitals",
34
+ "ContentTasksHandler",
35
+ "RedirectsHandler",
36
+ "TagsHandler",
37
+ "SettingsHandler",
38
+ "ResourcesHandler",
39
+ "ResourcesAPIHandler",
40
+ "ResourceChangeHandler",
41
+ "ResourceClonesHandler",
42
+ "ResourceDeletesHandler",
43
+ "ResourceExportsHandler",
44
+ "ResourceHistoryHandler",
45
+ "ResourceTranslationsHandler",
46
+ "ResourceURLsHandler",
47
+ "ResourceRevisionsHandler",
48
+ "ResourceRevisionsGeneralHandler",
49
+ "ResourceRevisionsVisualHandler",
50
+ "ResourceRevisionsCodeHandler",
51
+ "ResourceRevisionsMetaHandler",
52
+ "ResourceRevisionsTagHandler",
53
+ "ResourceRevisionsLocksHandler",
54
+ "ResourceRevisionsUnlocksHandler",
55
+ "ResourceRevisionsPublishesHandler",
56
+ "ResourceRevisionsRevertsHandler",
57
+ "ResourceRevisionsWidgetHandler",
58
+ "getResourceUpdateDestination",
59
+ "Factory",
60
+ "Logger",
61
+ ];
62
+
63
+ test.each(EXPECTED_EXPORTS)("exports %s", (name) => {
64
+ expect(adminPackage[name]).toBeDefined();
65
+ });
66
+
67
+ test("exports nothing undefined", () => {
68
+ const undefinedExports = Object.keys(adminPackage).filter(
69
+ (name) => adminPackage[name] === undefined,
70
+ );
71
+
72
+ expect(undefinedExports).toEqual([]);
73
+ });
74
+ });