Skip to content

Commit 649817f

Browse files
adrians5jclaude
andauthored
refactor(cms-testing): DI-native function plugins; remove ContextPlugin from consumers (#5424)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 3b5f1a8 commit 649817f

19 files changed

Lines changed: 101 additions & 95 deletions

File tree

packages/api-aco/__tests__/folder.flp.inheritance.test.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { describe, it, expect } from "vitest";
2-
import { ContextPlugin } from "@webiny/api";
2+
import type { Container } from "@webiny/di";
33
import { useGraphQlHandler } from "./utils/useGraphQlHandler";
44
import { TeamFactory } from "@webiny/api-core/features/security/teams/shared/abstractions.js";
55
import type { IdentityData } from "@webiny/api-core/features/security/IdentityContext";
6-
import type { ApiCoreContext } from "@webiny/api-core/types/core.js";
76

87
const FOLDER_TYPE = "test-folders";
98

@@ -32,9 +31,9 @@ describe("Folder Level Permissions - Inheritance", () => {
3231
const { aco } = useGraphQlHandler({
3332
identity: identityA,
3433
plugins: [
35-
new ContextPlugin<ApiCoreContext>(async context => {
36-
context.container.registerInstance(TeamFactory, new TestTeamFactory());
37-
})
34+
(container: Container) => {
35+
container.registerInstance(TeamFactory, new TestTeamFactory());
36+
}
3837
]
3938
});
4039

packages/api-aco/__tests__/mocks/lifecycle.mock.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { ContextPlugin } from "@webiny/api";
2-
import type { AcoContext } from "~/types";
1+
import type { Container } from "@webiny/di";
32
import { LifecycleEventTracker } from "@webiny/project-utils/testing/helpers/lifecycleTracker";
43
import type { DomainEvent } from "@webiny/api-core/features/eventPublisher/index.js";
54
import {
@@ -24,41 +23,41 @@ import {
2423
export const tracker = new LifecycleEventTracker();
2524

2625
export const assignFolderLifecycleEvents = () => {
27-
return new ContextPlugin<AcoContext>(async context => {
28-
context.container.registerFactory(FolderBeforeCreateEventHandler, () => ({
26+
return (container: Container) => {
27+
container.registerFactory(FolderBeforeCreateEventHandler, () => ({
2928
async handle(event: DomainEvent<FolderBeforeCreatePayload>) {
3029
tracker.track("folder:beforeCreate", event.payload);
3130
}
3231
}));
3332

34-
context.container.registerFactory(FolderAfterCreateEventHandler, () => ({
33+
container.registerFactory(FolderAfterCreateEventHandler, () => ({
3534
async handle(event: DomainEvent<FolderAfterCreatePayload>) {
3635
tracker.track("folder:afterCreate", event.payload);
3736
}
3837
}));
3938

40-
context.container.registerFactory(FolderBeforeUpdateEventHandler, () => ({
39+
container.registerFactory(FolderBeforeUpdateEventHandler, () => ({
4140
async handle(event: DomainEvent<FolderBeforeUpdatePayload>) {
4241
tracker.track("folder:beforeUpdate", event.payload);
4342
}
4443
}));
4544

46-
context.container.registerFactory(FolderAfterUpdateEventHandler, () => ({
45+
container.registerFactory(FolderAfterUpdateEventHandler, () => ({
4746
async handle(event: DomainEvent<FolderAfterUpdatePayload>) {
4847
tracker.track("folder:afterUpdate", event.payload);
4948
}
5049
}));
5150

52-
context.container.registerFactory(FolderBeforeDeleteEventHandler, () => ({
51+
container.registerFactory(FolderBeforeDeleteEventHandler, () => ({
5352
async handle(event: DomainEvent<FolderBeforeDeletePayload>) {
5453
tracker.track("folder:beforeDelete", event.payload);
5554
}
5655
}));
5756

58-
context.container.registerFactory(FolderAfterDeleteEventHandler, () => ({
57+
container.registerFactory(FolderAfterDeleteEventHandler, () => ({
5958
async handle(event: DomainEvent<FolderAfterDeletePayload>) {
6059
tracker.track("folder:afterDelete", event.payload);
6160
}
6261
}));
63-
});
62+
};
6463
};

packages/api-aco/__tests__/utils/useGraphQlHandler.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,19 @@ export const useGraphQlHandler = (params: UseGQLHandlerParams = {}) => {
5757
processLegacyPlugins(container, apiAcoStorage.plugins);
5858

5959
if (params.plugins) {
60-
const extraPlugins = [params.plugins].flat(Infinity as 1);
61-
for (const plugin of extraPlugins) {
60+
const extraPlugins = [params.plugins].flat(Infinity as 1).filter(Boolean);
61+
// DI-native plugins are plain `container => {}` functions; call them directly.
62+
const isFn = (p: any) => typeof p === "function" && !p.prototype;
63+
for (const plugin of extraPlugins.filter(isFn)) {
64+
(plugin as (c: any) => void)(container);
65+
}
66+
const rest = extraPlugins.filter(p => !isFn(p));
67+
for (const plugin of rest) {
6268
if (plugin instanceof ContextPlugin) {
6369
await plugin.apply({ container } as any);
6470
}
6571
}
66-
processLegacyPlugins(container, extraPlugins);
72+
processLegacyPlugins(container, rest);
6773
}
6874

6975
container.register(FileModel);

packages/api-headless-cms-aco/__tests__/mocks/lifecycle.mock.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
EntryAfterRestoreFromBinEventHandler,
44
EntryBeforeRestoreFromBinEventHandler
55
} from "@webiny/api-headless-cms/features/contentEntry/RestoreEntryFromBin/events.js";
6-
import { ContextPlugin } from "@webiny/api";
6+
import type { Container } from "@webiny/di";
77

88
export const tracker = new LifecycleEventTracker();
99

@@ -20,14 +20,14 @@ class TrackEntryAfterRestoreHandler implements EntryAfterRestoreFromBinEventHand
2020
}
2121

2222
export const assignCmsLifecycleEvents = () => {
23-
return new ContextPlugin(context => {
24-
context.container.registerFactory(
23+
return (container: Container) => {
24+
container.registerFactory(
2525
EntryBeforeRestoreFromBinEventHandler,
2626
() => new TrackEntryBeforeRestoreHandler()
2727
);
28-
context.container.registerFactory(
28+
container.registerFactory(
2929
EntryAfterRestoreFromBinEventHandler,
3030
() => new TrackEntryAfterRestoreHandler()
3131
);
32-
});
32+
};
3333
};

packages/api-headless-cms-aco/__tests__/utils/useGraphQlHandler.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,12 @@ export const useGraphQlHandler = (params: UseGQLHandlerParams = {}) => {
6565
const resolvedIdentity = identity ?? defaultIdentity;
6666
const resolvedPermissions = permissions ?? ([{ name: "*" }] as SecurityPermission[]);
6767

68-
const extraCmsPlugins = ([plugins] as any[]).flat(Infinity as 1).filter(Boolean);
68+
const allPlugins = ([plugins] as any[]).flat(Infinity as 1).filter(Boolean);
69+
// DI-native plugins are plain `container => {}` functions; call them after features (below).
70+
// Everything else (static CMS plugins) is forwarded to HeadlessCmsFeature.extraPlugins.
71+
const isFn = (p: any) => typeof p === "function" && !p.prototype;
72+
const fnPlugins = allPlugins.filter(isFn);
73+
const extraCmsPlugins = allPlugins.filter(p => !isFn(p));
6974

7075
const handler = createTestHttpHandler({
7176
root: container => {
@@ -91,6 +96,11 @@ export const useGraphQlHandler = (params: UseGQLHandlerParams = {}) => {
9196
container.register(HeadlessCmsContextualSchema);
9297
AcoFeature.register(container);
9398
AcoHcmsFeature.register(container);
99+
// DI-native function plugins run after the package's features so they can override
100+
// defaults (last-wins), mirroring the legacy ContextPlugin timing.
101+
for (const plugin of fnPlugins) {
102+
(plugin as (c: any) => void)(container);
103+
}
94104
GraphQLEngineFeature.register(container);
95105
}
96106
});

packages/api-headless-cms-aco/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
"typescript": "^7.0.2"
3535
},
3636
"dependencies": {
37-
"@webiny/api": "0.0.0",
3837
"@webiny/api-aco": "0.0.0",
3938
"@webiny/api-headless-cms": "0.0.0",
4039
"@webiny/feature": "0.0.0",

packages/api-headless-cms-aco/tsconfig.build.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
"extends": "../../tsconfig.build.json",
33
"include": ["src"],
44
"references": [
5-
{ "path": "../api/tsconfig.build.json" },
65
{ "path": "../api-aco/tsconfig.build.json" },
76
{ "path": "../api-headless-cms/tsconfig.build.json" },
87
{ "path": "../feature/tsconfig.build.json" },
@@ -21,8 +20,6 @@
2120
"paths": {
2221
"~/*": ["./src/*"],
2322
"~tests/*": ["./__tests__/*"],
24-
"@webiny/api/*": ["../api/src/*"],
25-
"@webiny/api": ["../api/src"],
2623
"@webiny/api-aco/*": ["../api-aco/src/*"],
2724
"@webiny/api-aco": ["../api-aco/src"],
2825
"@webiny/api-headless-cms/*": ["../api-headless-cms/src/*"],

packages/api-headless-cms-aco/tsconfig.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
"extends": "../../tsconfig.json",
33
"include": ["src", "__tests__"],
44
"references": [
5-
{ "path": "../api" },
65
{ "path": "../api-aco" },
76
{ "path": "../api-headless-cms" },
87
{ "path": "../feature" },
@@ -21,8 +20,6 @@
2120
"paths": {
2221
"~/*": ["./src/*"],
2322
"~tests/*": ["./__tests__/*"],
24-
"@webiny/api/*": ["../api/src/*"],
25-
"@webiny/api": ["../api/src"],
2623
"@webiny/api-aco/*": ["../api-aco/src/*"],
2724
"@webiny/api-aco": ["../api-aco/src"],
2825
"@webiny/api-headless-cms/*": ["../api-headless-cms/src/*"],

packages/api-headless-cms-bulk-actions/__tests__/context/useGraphQLHandler.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,19 @@ interface InvokeParams {
3030
export const useGraphQlHandler = (params: UseGQLHandlerParams = {}) => {
3131
const { plugins = [] } = params;
3232

33-
const extraCmsPlugins = ([plugins] as any[]).flat(Infinity as 1).filter(Boolean);
33+
const allPlugins = ([plugins] as any[]).flat(Infinity as 1).filter(Boolean);
34+
// DI-native plugins are plain `container => {}` functions → the `plugins` param (createCmsTestHandler
35+
// calls them after features). Static CMS plugins (e.g. model plugins) → extraCmsPlugins.
36+
const isFn = (p: any) => typeof p === "function" && !p.prototype;
37+
const fnPlugins = allPlugins.filter(isFn);
38+
const extraCmsPlugins = allPlugins.filter(p => !isFn(p));
3439

3540
const { handler, invoke } = createCmsTestHandler({
3641
identity: params.identity ?? createIdentity(),
3742
permissions: params.permissions ?? (createPermissions() as SecurityPermission[]),
3843
testProjectLicense: params.testProjectLicense,
3944
extraCmsPlugins,
45+
plugins: fnPlugins,
4046
features: container => {
4147
container.register(HeadlessCmsContextualSchema);
4248
// Background tasks + bulk actions are DI-native now.

packages/api-headless-cms-bulk-actions/__tests__/tasks/createBulkAction.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
22
import { IntrospectionField, IntrospectionInterfaceType } from "graphql";
33
import { useGraphQlHandler } from "~tests/context/useGraphQLHandler";
44
import { createMockModels, createPrivateMockModels } from "~tests/mocks";
5-
import { createContextPlugin } from "@webiny/api";
5+
import type { Container } from "@webiny/di";
66
import { EntriesBulkAction } from "~/features/EntriesBulkAction/abstractions.js";
77

88
interface GraphQlType {
@@ -92,9 +92,9 @@ describe("createBulkAction", () => {
9292
const { introspect } = useGraphQlHandler({
9393
plugins: [
9494
...createMockModels(),
95-
createContextPlugin(context => {
96-
context.container.registerInstance(EntriesBulkAction, new PrintBulkAction());
97-
})
95+
(container: Container) => {
96+
container.registerInstance(EntriesBulkAction, new PrintBulkAction());
97+
}
9898
]
9999
});
100100

0 commit comments

Comments
 (0)