@devvit/test 0.12.6-next-2025-12-05-00-07-24-65a40a4e6.0 → 0.12.6-next-2025-12-05-20-00-45-8a5827c28.0
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 +14 -4
- package/server/index.d.ts +1 -1
- package/server/index.d.ts.map +1 -1
- package/server/index.js +1 -1
- package/server/vitest/context.d.ts +10 -0
- package/server/vitest/context.d.ts.map +1 -0
- package/server/vitest/context.js +25 -0
- package/server/vitest/context.test.d.ts.map +1 -0
- package/server/vitest/devvitTest.d.ts +102 -0
- package/server/vitest/devvitTest.d.ts.map +1 -0
- package/server/vitest/devvitTest.js +117 -0
- package/server/vitest/devvitTest.media.test.d.ts.map +1 -0
- package/server/vitest/devvitTest.test.d.ts.map +1 -0
- package/server/vitest/index.d.ts +2 -0
- package/server/vitest/index.d.ts.map +1 -0
- package/server/vitest/index.js +1 -0
- package/server/index.test.d.ts.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devvit/test",
|
|
3
|
-
"version": "0.12.6-next-2025-12-05-00-
|
|
3
|
+
"version": "0.12.6-next-2025-12-05-20-00-45-8a5827c28.0",
|
|
4
4
|
"license": "BSD-3-Clause",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
"browser": "./server/serverImportInClientCodePanic.js",
|
|
13
13
|
"default": "./server/index.js"
|
|
14
14
|
},
|
|
15
|
+
"./server/vitest": {
|
|
16
|
+
"browser": "./server/serverImportInClientCodePanic.js",
|
|
17
|
+
"default": "./server/vitest/index.js"
|
|
18
|
+
},
|
|
15
19
|
"./package.json": "./package.json"
|
|
16
20
|
},
|
|
17
21
|
"files": [
|
|
@@ -30,15 +34,21 @@
|
|
|
30
34
|
"test:unit": "vitest run",
|
|
31
35
|
"test:unit-with-coverage": "vitest run --coverage"
|
|
32
36
|
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@devvit/media": "0.12.6-next-2025-12-05-20-00-45-8a5827c28.0",
|
|
39
|
+
"@devvit/protos": "0.12.6-next-2025-12-05-20-00-45-8a5827c28.0",
|
|
40
|
+
"@devvit/server": "0.12.6-next-2025-12-05-20-00-45-8a5827c28.0",
|
|
41
|
+
"@devvit/shared-types": "0.12.6-next-2025-12-05-20-00-45-8a5827c28.0"
|
|
42
|
+
},
|
|
33
43
|
"peerDependencies": {
|
|
34
44
|
"vitest": "*"
|
|
35
45
|
},
|
|
36
46
|
"devDependencies": {
|
|
37
|
-
"@devvit/repo-tools": "0.12.6-next-2025-12-05-00-
|
|
38
|
-
"@devvit/tsconfig": "0.12.6-next-2025-12-05-00-
|
|
47
|
+
"@devvit/repo-tools": "0.12.6-next-2025-12-05-20-00-45-8a5827c28.0",
|
|
48
|
+
"@devvit/tsconfig": "0.12.6-next-2025-12-05-20-00-45-8a5827c28.0",
|
|
39
49
|
"eslint": "9.11.1",
|
|
40
50
|
"typescript": "5.8.3",
|
|
41
51
|
"vitest": "1.6.1"
|
|
42
52
|
},
|
|
43
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "8fe5d0140935bfdd2604f96d4404810e20f2c896"
|
|
44
54
|
}
|
package/server/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export
|
|
1
|
+
export * from '@devvit/media/test';
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
package/server/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC"}
|
package/server/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export * from '@devvit/media/test';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Config } from '@devvit/shared-types/Config.js';
|
|
2
|
+
import type { AppConfig } from '@devvit/shared-types/schemas/config-file.v1.js';
|
|
3
|
+
export type TestContext = {
|
|
4
|
+
config: Config;
|
|
5
|
+
appConfig?: AppConfig | undefined;
|
|
6
|
+
};
|
|
7
|
+
export declare function runWithTestContext<T>(context: TestContext, fn: () => T): T;
|
|
8
|
+
export declare function getTestContext(): TestContext | undefined;
|
|
9
|
+
export declare function installDevvitInterceptors(): void;
|
|
10
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/server/vitest/context.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gCAAgC,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gDAAgD,CAAC;AAGhF,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CACnC,CAAC;AAIF,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAE1E;AAED,wBAAgB,cAAc,IAAI,WAAW,GAAG,SAAS,CAExD;AAcD,wBAAgB,yBAAyB,SAIxC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import {} from '@devvit/shared-types/shared/devvit-worker-global.js';
|
|
3
|
+
const contextStorage = new AsyncLocalStorage();
|
|
4
|
+
export function runWithTestContext(context, fn) {
|
|
5
|
+
return contextStorage.run(context, fn);
|
|
6
|
+
}
|
|
7
|
+
export function getTestContext() {
|
|
8
|
+
return contextStorage.getStore();
|
|
9
|
+
}
|
|
10
|
+
class TestDevvitWorkerGlobal {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.compute = { platform: 'node' };
|
|
13
|
+
}
|
|
14
|
+
get config() {
|
|
15
|
+
return getTestContext()?.config;
|
|
16
|
+
}
|
|
17
|
+
get appConfig() {
|
|
18
|
+
return getTestContext()?.appConfig;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function installDevvitInterceptors() {
|
|
22
|
+
if (globalThis.devvit instanceof TestDevvitWorkerGlobal)
|
|
23
|
+
return;
|
|
24
|
+
globalThis.devvit = new TestDevvitWorkerGlobal();
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.test.d.ts","sourceRoot":"","sources":["../../../src/server/vitest/context.test.ts"],"names":[],"mappings":"AAAA,OAAO,qDAAqD,CAAC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { MediaMock } from '@devvit/media/test';
|
|
2
|
+
import type { Config } from '@devvit/shared-types/Config.js';
|
|
3
|
+
import { Header } from '@devvit/shared-types/Header.js';
|
|
4
|
+
import type { AppConfig } from '@devvit/shared-types/schemas/config-file.v1.js';
|
|
5
|
+
import type { T2, T5 } from '@devvit/shared-types/tid.js';
|
|
6
|
+
import type { TestAPI } from 'vitest';
|
|
7
|
+
export type DevvitFixtures = {
|
|
8
|
+
/**
|
|
9
|
+
* The Devvit configuration object for the current test context.
|
|
10
|
+
*/
|
|
11
|
+
config: Config;
|
|
12
|
+
/**
|
|
13
|
+
* Request headers simulating a real Devvit execution environment.
|
|
14
|
+
*/
|
|
15
|
+
headers: {
|
|
16
|
+
[key in Header]?: string;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* App settings configured for this test instance.
|
|
20
|
+
* Can be customized via `createDevvitTest({ settings: ... })`.
|
|
21
|
+
*/
|
|
22
|
+
settings: {
|
|
23
|
+
[key: string]: string | number | boolean | string[] | undefined;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* The username of the simulated user executing the app.
|
|
27
|
+
* @default 'testuser'
|
|
28
|
+
*/
|
|
29
|
+
username: string;
|
|
30
|
+
/**
|
|
31
|
+
* The user ID (t2_*) of the simulated user.
|
|
32
|
+
* @default 't2_testuser'
|
|
33
|
+
*/
|
|
34
|
+
userId: T2;
|
|
35
|
+
/**
|
|
36
|
+
* The name of the subreddit where the app is running.
|
|
37
|
+
* @default 'testsub'
|
|
38
|
+
*/
|
|
39
|
+
subredditName: string;
|
|
40
|
+
/**
|
|
41
|
+
* The subreddit ID (t5_*) where the app is running.
|
|
42
|
+
* @default 't5_testsub'
|
|
43
|
+
*/
|
|
44
|
+
subredditId: T5;
|
|
45
|
+
/**
|
|
46
|
+
* Direct access to mock implementations of Devvit capabilities.
|
|
47
|
+
* Use these to inspect state or configure specific mock behaviors.
|
|
48
|
+
*/
|
|
49
|
+
mocks: {
|
|
50
|
+
media: MediaMock;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
export type DevvitTestConfig = {
|
|
54
|
+
/**
|
|
55
|
+
* The username of the simulated user.
|
|
56
|
+
* @default 'testuser'
|
|
57
|
+
*/
|
|
58
|
+
username?: string;
|
|
59
|
+
/**
|
|
60
|
+
* The user ID (t2_*) of the simulated user.
|
|
61
|
+
* @default 't2_testuser'
|
|
62
|
+
*/
|
|
63
|
+
userId?: T2;
|
|
64
|
+
/**
|
|
65
|
+
* The name of the subreddit where the app is running.
|
|
66
|
+
* @default 'testsub'
|
|
67
|
+
*/
|
|
68
|
+
subredditName?: string;
|
|
69
|
+
/**
|
|
70
|
+
* The subreddit ID (t5_*) where the app is running.
|
|
71
|
+
* @default 't5_testsub'
|
|
72
|
+
*/
|
|
73
|
+
subredditId?: T5;
|
|
74
|
+
/**
|
|
75
|
+
* Pre-configured app settings to be available via `context.settings`. Use to set
|
|
76
|
+
* settings for use with the `@devvit/settings` capability to make it easier to test.
|
|
77
|
+
*/
|
|
78
|
+
settings?: {
|
|
79
|
+
[key: string]: string | number | boolean | string[] | undefined;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Custom AppConfig to use for the test environment. For internal use.
|
|
83
|
+
*/
|
|
84
|
+
appConfig?: AppConfig;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Creates a test runner with customized Devvit environment.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* const test = createDevvitTest({
|
|
91
|
+
* username: 'moderator_bob',
|
|
92
|
+
* settings: { enable_ban_hammer: true }
|
|
93
|
+
* });
|
|
94
|
+
*
|
|
95
|
+
* test('mod action works', async ({ config, headers }) => {
|
|
96
|
+
* // ...
|
|
97
|
+
* });
|
|
98
|
+
*
|
|
99
|
+
* @experimental
|
|
100
|
+
*/
|
|
101
|
+
export declare const createDevvitTest: (config?: DevvitTestConfig) => TestAPI<DevvitFixtures>;
|
|
102
|
+
//# sourceMappingURL=devvitTest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devvitTest.d.ts","sourceRoot":"","sources":["../../../src/server/vitest/devvitTest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAG/C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gCAAgC,CAAC;AAC7D,OAAO,EAAE,MAAM,EAAE,MAAM,gCAAgC,CAAC;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gDAAgD,CAAC;AAMhF,OAAO,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,KAAK,EAAE,OAAO,EAAgB,MAAM,QAAQ,CAAC;AAOpD,MAAM,MAAM,cAAc,GAAG;IAC3B;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,OAAO,EAAE;SAAG,GAAG,IAAI,MAAM,CAAC,CAAC,EAAE,MAAM;KAAE,CAAC;IAEtC;;;OAGG;IACH,QAAQ,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,GAAG,SAAS,CAAA;KAAE,CAAC;IAE9E;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,MAAM,EAAE,EAAE,CAAC;IAEX;;;OAGG;IACH,aAAa,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,WAAW,EAAE,EAAE,CAAC;IAEhB;;;OAGG;IACH,KAAK,EAAE;QACL,KAAK,EAAE,SAAS,CAAC;KAClB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,MAAM,CAAC,EAAE,EAAE,CAAC;IAEZ;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,WAAW,CAAC,EAAE,EAAE,CAAC;IAEjB;;;OAGG;IACH,QAAQ,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,GAAG,SAAS,CAAA;KAAE,CAAC;IAE/E;;OAEG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB,CAAC;AA+HF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,gBAAgB,GAAI,SAAQ,gBAAqB,KAAG,OAAO,CAAC,cAAc,CAEtF,CAAC"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { MediaMock } from '@devvit/media/test';
|
|
2
|
+
import { MediaServiceDefinition } from '@devvit/protos/types//devvit/plugin/media/media.js';
|
|
3
|
+
import { Context, runWithContext } from '@devvit/server';
|
|
4
|
+
import { Header } from '@devvit/shared-types/Header.js';
|
|
5
|
+
import { getDefaultAppConfig, makeConfig, MOCK_HEADERS as headersMock, } from '@devvit/shared-types/test/index.js';
|
|
6
|
+
import { test as baseTest } from 'vitest';
|
|
7
|
+
import { installDevvitInterceptors, runWithTestContext } from './context.js';
|
|
8
|
+
installDevvitInterceptors();
|
|
9
|
+
function createWrappedTestApi(target, setup) {
|
|
10
|
+
return new Proxy(target, {
|
|
11
|
+
// Handles: test(...), test.only(...), test.skip(...),
|
|
12
|
+
// test.each(table)(...), test.concurrent(...), etc.
|
|
13
|
+
apply(fn, thisArg, argArray) {
|
|
14
|
+
const args = [...argArray];
|
|
15
|
+
// Find the last function argument (the test body / hook body)
|
|
16
|
+
let lastFnIndex = -1;
|
|
17
|
+
for (let i = args.length - 1; i >= 0; i--) {
|
|
18
|
+
if (typeof args[i] === 'function') {
|
|
19
|
+
lastFnIndex = i;
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (lastFnIndex !== -1) {
|
|
24
|
+
const userFn = args[lastFnIndex];
|
|
25
|
+
// Wrap the user's test function
|
|
26
|
+
args[lastFnIndex] = (...fnArgs) => {
|
|
27
|
+
const { reqCtx, fixtures, testContext } = setup();
|
|
28
|
+
// Heuristic to detect Vitest TestContext
|
|
29
|
+
// Standard test() calls receive a single TestContext object.
|
|
30
|
+
// test.each() calls receive the parameters as arguments.
|
|
31
|
+
const isTestContext = fnArgs.length === 1 &&
|
|
32
|
+
fnArgs[0] &&
|
|
33
|
+
(typeof fnArgs[0] === 'object' || typeof fnArgs[0] === 'function') &&
|
|
34
|
+
'task' in fnArgs[0] &&
|
|
35
|
+
// Vitest context usually has these lifecycle methods
|
|
36
|
+
'onTestFailed' in fnArgs[0];
|
|
37
|
+
if (isTestContext) {
|
|
38
|
+
const mergedCtx = { ...fixtures, ...fnArgs[0] };
|
|
39
|
+
return runWithContext(reqCtx, () => runWithTestContext(testContext, () => userFn(mergedCtx)));
|
|
40
|
+
}
|
|
41
|
+
return runWithContext(reqCtx, () => runWithTestContext(testContext, () => userFn(...fnArgs)));
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const result = Reflect.apply(fn, thisArg, args);
|
|
45
|
+
// IMPORTANT for `.each` (and any curried API):
|
|
46
|
+
// if the result is a function, wrap it too so subsequent call(s)
|
|
47
|
+
// also get fixtures + ALS.
|
|
48
|
+
if (typeof result === 'function') {
|
|
49
|
+
return createWrappedTestApi(result, setup);
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
},
|
|
53
|
+
// Handles property access: test.only, test.skip, test.each, test.concurrent, ...
|
|
54
|
+
get(fn, prop, receiver) {
|
|
55
|
+
const value = Reflect.get(fn, prop, receiver);
|
|
56
|
+
if (typeof value === 'function') {
|
|
57
|
+
return createWrappedTestApi(value, setup);
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const setup = (config) => {
|
|
64
|
+
const username = config.username ?? 'testuser';
|
|
65
|
+
const userId = config.userId ?? 't2_testuser';
|
|
66
|
+
const subredditName = config.subredditName ?? 'testsub';
|
|
67
|
+
const subredditId = config.subredditId ?? 't5_testsub';
|
|
68
|
+
const settings = config.settings ?? {};
|
|
69
|
+
const appConfig = config.appConfig;
|
|
70
|
+
const headers = {
|
|
71
|
+
...headersMock,
|
|
72
|
+
[Header.User]: userId,
|
|
73
|
+
[Header.Username]: username,
|
|
74
|
+
[Header.AppUser]: userId,
|
|
75
|
+
[Header.Subreddit]: subredditId,
|
|
76
|
+
[Header.SubredditName]: subredditName,
|
|
77
|
+
};
|
|
78
|
+
const mediaMock = new MediaMock();
|
|
79
|
+
const cfg = makeConfig({
|
|
80
|
+
plugins: {
|
|
81
|
+
[MediaServiceDefinition.fullName]: mediaMock,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
const contextAppConfig = appConfig ?? getDefaultAppConfig();
|
|
85
|
+
const reqCtx = Context(headers);
|
|
86
|
+
const fixtures = {
|
|
87
|
+
config: cfg,
|
|
88
|
+
headers,
|
|
89
|
+
settings,
|
|
90
|
+
username,
|
|
91
|
+
userId,
|
|
92
|
+
subredditName,
|
|
93
|
+
subredditId,
|
|
94
|
+
mocks: {
|
|
95
|
+
media: mediaMock,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
return { reqCtx, fixtures, testContext: { config: cfg, appConfig: contextAppConfig } };
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Creates a test runner with customized Devvit environment.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* const test = createDevvitTest({
|
|
105
|
+
* username: 'moderator_bob',
|
|
106
|
+
* settings: { enable_ban_hammer: true }
|
|
107
|
+
* });
|
|
108
|
+
*
|
|
109
|
+
* test('mod action works', async ({ config, headers }) => {
|
|
110
|
+
* // ...
|
|
111
|
+
* });
|
|
112
|
+
*
|
|
113
|
+
* @experimental
|
|
114
|
+
*/
|
|
115
|
+
export const createDevvitTest = (config = {}) => {
|
|
116
|
+
return createWrappedTestApi(baseTest, () => setup(config));
|
|
117
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devvitTest.media.test.d.ts","sourceRoot":"","sources":["../../../src/server/vitest/devvitTest.media.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devvitTest.test.d.ts","sourceRoot":"","sources":["../../../src/server/vitest/devvitTest.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/vitest/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './devvitTest.js';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../src/server/index.test.ts"],"names":[],"mappings":""}
|