orpc-nuxt 0.2.0 → 0.3.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/README.md +42 -31
- package/dist/module.json +1 -1
- package/dist/runtime/testing.d.ts +34 -0
- package/dist/runtime/testing.js +60 -0
- package/package.json +11 -1
package/README.md
CHANGED
|
@@ -432,66 +432,77 @@ const orpc = createORPCNuxtClient(client, { prefix: "blog" })
|
|
|
432
432
|
|
|
433
433
|
### Component tests
|
|
434
434
|
|
|
435
|
-
|
|
435
|
+
Configure the Nuxt test environment and load a shared setup file:
|
|
436
436
|
|
|
437
437
|
```ts
|
|
438
438
|
// vitest.config.ts
|
|
439
439
|
import { defineVitestConfig } from "@nuxt/test-utils/config"
|
|
440
440
|
|
|
441
|
-
export default defineVitestConfig({
|
|
441
|
+
export default defineVitestConfig({
|
|
442
|
+
test: {
|
|
443
|
+
setupFiles: ["test/nuxt/setup.ts"],
|
|
444
|
+
},
|
|
445
|
+
})
|
|
442
446
|
```
|
|
443
447
|
|
|
444
448
|
That configuration runs every test under `test/nuxt/` or `tests/nuxt/`, and every test named `*.nuxt.test.ts` or `*.nuxt.spec.ts`, against your application.
|
|
445
|
-
|
|
446
|
-
|
|
449
|
+
Use `createTestORPCClient()` from `orpc-nuxt/testing` to replace `useOrpc()` with an isolated fake client.
|
|
450
|
+
`client` includes the regular composables and oRPC utilities.
|
|
451
|
+
Each procedure leaf in `procedures` has a `.handle()` method that registers a typed handler and returns a Vitest mock.
|
|
447
452
|
|
|
448
453
|
```ts
|
|
449
|
-
// test/nuxt/
|
|
450
|
-
import { mockNuxtImport
|
|
454
|
+
// test/nuxt/setup.ts
|
|
455
|
+
import { mockNuxtImport } from "@nuxt/test-utils/runtime"
|
|
456
|
+
import type { RouterClient } from "@orpc/server"
|
|
451
457
|
import { QueryClient } from "@tanstack/vue-query"
|
|
452
|
-
import {
|
|
453
|
-
import { afterEach
|
|
458
|
+
import { createTestORPCClient } from "orpc-nuxt/testing"
|
|
459
|
+
import { afterEach } from "vitest"
|
|
454
460
|
|
|
455
|
-
import
|
|
461
|
+
import type { router } from "~~/server/rpc/router"
|
|
456
462
|
|
|
457
463
|
const queryClient = new QueryClient({
|
|
458
464
|
// Report a failing procedure instead of retrying it until the test times out.
|
|
459
465
|
defaultOptions: { queries: { retry: false } },
|
|
460
466
|
})
|
|
461
467
|
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
posts: {
|
|
466
|
-
list: async () => [{ id: 1, title: "First post" }],
|
|
467
|
-
},
|
|
468
|
-
},
|
|
469
|
-
},
|
|
470
|
-
{ queryClient },
|
|
471
|
-
)
|
|
468
|
+
export const { client, procedures, reset } = createTestORPCClient<RouterClient<typeof router>>({
|
|
469
|
+
queryClient,
|
|
470
|
+
})
|
|
472
471
|
|
|
473
|
-
// Vitest
|
|
474
|
-
mockNuxtImport("useOrpc", () => () =>
|
|
472
|
+
// Return the composable itself; Vitest hoists this factory before the setup file runs.
|
|
473
|
+
mockNuxtImport("useOrpc", () => () => client)
|
|
475
474
|
|
|
476
475
|
afterEach(() => {
|
|
477
|
-
//
|
|
478
|
-
queryClient.clear()
|
|
476
|
+
reset() // Remove registered handlers.
|
|
477
|
+
queryClient.clear() // Remove cached responses.
|
|
479
478
|
})
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
`orpc-nuxt/testing` does not import `nuxt/app`, so the hoisted `mockNuxtImport()` factory can load it safely.
|
|
482
|
+
The explicit QueryClient keeps the test cache isolated and lets the setup clear it after each test.
|
|
483
|
+
To use the application's QueryClient instead, omit the option and configure its defaults through `orpc.queryClient`.
|
|
484
|
+
In that mode, import `useOrpcQueryClient()` only from a module that the hoisted factory cannot reach.
|
|
485
|
+
|
|
486
|
+
Register the required handlers before mounting; `mountSuspended()` waits for awaited queries before assertions:
|
|
487
|
+
|
|
488
|
+
```ts
|
|
489
|
+
// test/nuxt/post-list.spec.ts
|
|
490
|
+
import { mountSuspended } from "@nuxt/test-utils/runtime"
|
|
491
|
+
import { expect, test } from "vitest"
|
|
492
|
+
|
|
493
|
+
import PostList from "~/components/post-list.vue"
|
|
494
|
+
|
|
495
|
+
import { procedures } from "./setup"
|
|
480
496
|
|
|
481
497
|
test("renders the posts", async () => {
|
|
498
|
+
const list = procedures.blog.posts.list.handle(() => [{ id: 1, title: "First post" }])
|
|
482
499
|
const component = await mountSuspended(PostList)
|
|
500
|
+
|
|
483
501
|
expect(component.text()).toContain("First post")
|
|
502
|
+
expect(list).toHaveBeenCalledOnce()
|
|
484
503
|
})
|
|
485
504
|
```
|
|
486
505
|
|
|
487
|
-
Passing an explicit `queryClient` gives the tests their own cache:
|
|
488
|
-
|
|
489
|
-
- The application's cache never carries data from one test into the next.
|
|
490
|
-
- Test defaults such as `retry: false` stay in the test file instead of the module options.
|
|
491
|
-
|
|
492
|
-
Omit it to test against the cache the module installs.
|
|
493
|
-
Read that one with `useOrpcQueryClient()`, and set its defaults through the `orpc.queryClient` module options.
|
|
494
|
-
|
|
495
506
|
### Outside Vue components
|
|
496
507
|
|
|
497
508
|
You can call `.useQuery()` and `.useMutation()` outside a component, for example in a script or in a test that never mounts one.
|
package/dist/module.json
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { AnyNestedClient, Client } from "@orpc/client";
|
|
2
|
+
import type { Mock } from "vitest";
|
|
3
|
+
import type { ORPCNuxtClient, ORPCNuxtClientOptions } from "./types.js";
|
|
4
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
5
|
+
/** A procedure implementation registered for a test client. */
|
|
6
|
+
export type TestORPCHandler<TProcedure> = TProcedure extends Client<infer _Context, infer Input, infer Output, infer _Error> ? (input: Input) => MaybePromise<Awaited<Output>> : never;
|
|
7
|
+
/** Registration methods for one procedure in a test client. */
|
|
8
|
+
export interface TestORPCProcedure<TProcedure> {
|
|
9
|
+
/** Register the implementation used by subsequent calls and return its Vitest mock. */
|
|
10
|
+
handle(handler: TestORPCHandler<TProcedure>): Mock<TestORPCHandler<TProcedure>>;
|
|
11
|
+
}
|
|
12
|
+
/** A router-shaped tree whose procedure leaves register test implementations. */
|
|
13
|
+
export type TestORPCProcedures<T extends AnyNestedClient> = T extends Client<infer Context, infer Input, infer Output, infer Error> ? TestORPCProcedure<Client<Context, Input, Output, Error>> : {
|
|
14
|
+
[Key in keyof T]: T[Key] extends AnyNestedClient ? TestORPCProcedures<T[Key]> : never;
|
|
15
|
+
};
|
|
16
|
+
/** The isolated client, procedure registry and cleanup function created for a test suite. */
|
|
17
|
+
export interface TestORPCClient<T extends AnyNestedClient> {
|
|
18
|
+
/** The decorated client to return from a mocked `useOrpc()`. */
|
|
19
|
+
client: ORPCNuxtClient<T>;
|
|
20
|
+
/** Register typed implementations and receive Vitest mocks for call assertions. */
|
|
21
|
+
procedures: TestORPCProcedures<T>;
|
|
22
|
+
/** Remove every registered procedure implementation. */
|
|
23
|
+
reset: () => void;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Create an isolated fake oRPC client for Nuxt component tests.
|
|
27
|
+
* The entrypoint has no Nuxt runtime dependency.
|
|
28
|
+
* A shared setup file can therefore import it when Vitest hoists `mockNuxtImport()`.
|
|
29
|
+
*
|
|
30
|
+
* @param options - Cache key prefix and an optional test-owned QueryClient.
|
|
31
|
+
* @returns A decorated client, its typed registration tree and a registration reset function.
|
|
32
|
+
*/
|
|
33
|
+
export declare function createTestORPCClient<T extends AnyNestedClient>(options?: ORPCNuxtClientOptions): TestORPCClient<T>;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { createORPCClient } from "@orpc/client";
|
|
2
|
+
import { vi } from "vitest";
|
|
3
|
+
import { createORPCNuxtClient } from "./client/create.js";
|
|
4
|
+
export function createTestORPCClient(options = {}) {
|
|
5
|
+
const registrations = /* @__PURE__ */ new Map();
|
|
6
|
+
function registerHandler(path, handler) {
|
|
7
|
+
const mock = vi.fn(handler);
|
|
8
|
+
registrations.set(path, mock);
|
|
9
|
+
return mock;
|
|
10
|
+
}
|
|
11
|
+
async function callHandler(path, input) {
|
|
12
|
+
const handler = registrations.get(path);
|
|
13
|
+
if (!handler) {
|
|
14
|
+
throw new Error(`No test handler is registered for oRPC procedure "${path}"`);
|
|
15
|
+
}
|
|
16
|
+
return await handler(input);
|
|
17
|
+
}
|
|
18
|
+
const link = {
|
|
19
|
+
async call(path, input) {
|
|
20
|
+
return await callHandler(path.join("."), input);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const client = createORPCNuxtClient(createORPCClient(link), options);
|
|
24
|
+
const procedures = createRecursiveProxy([], (path, args) => {
|
|
25
|
+
if (path.at(-1) !== "handle") {
|
|
26
|
+
throw new Error(`Unknown test procedure call: ${path.join(".")}`);
|
|
27
|
+
}
|
|
28
|
+
const handler = args[0];
|
|
29
|
+
if (typeof handler !== "function") {
|
|
30
|
+
throw new TypeError(
|
|
31
|
+
`A test handler must be a function for oRPC procedure "${path.slice(0, -1).join(".")}"`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return registerHandler(path.slice(0, -1).join("."), handler);
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
client,
|
|
38
|
+
procedures,
|
|
39
|
+
reset: () => registrations.clear()
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function createRecursiveProxy(path, call) {
|
|
43
|
+
const target = () => {
|
|
44
|
+
};
|
|
45
|
+
const children = /* @__PURE__ */ new Map();
|
|
46
|
+
return new Proxy(target, {
|
|
47
|
+
get(_, property, receiver) {
|
|
48
|
+
if (typeof property !== "string" || property === "then") {
|
|
49
|
+
return Reflect.get(target, property, receiver);
|
|
50
|
+
}
|
|
51
|
+
if (children.has(property)) return children.get(property);
|
|
52
|
+
const child = createRecursiveProxy([...path, property], call);
|
|
53
|
+
children.set(property, child);
|
|
54
|
+
return child;
|
|
55
|
+
},
|
|
56
|
+
apply(_, __, args) {
|
|
57
|
+
return call(path, args);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orpc-nuxt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "oRPC integration for Nuxt with TanStack Vue Query composables.",
|
|
5
5
|
"homepage": "https://github.com/IlyaSemenov/orpc-nuxt#readme",
|
|
6
6
|
"bugs": "https://github.com/IlyaSemenov/orpc-nuxt/issues",
|
|
@@ -33,6 +33,10 @@
|
|
|
33
33
|
"types": "./dist/runtime/plugin.d.ts",
|
|
34
34
|
"import": "./dist/runtime/plugin.js"
|
|
35
35
|
},
|
|
36
|
+
"./testing": {
|
|
37
|
+
"types": "./dist/runtime/testing.d.ts",
|
|
38
|
+
"import": "./dist/runtime/testing.js"
|
|
39
|
+
},
|
|
36
40
|
"./package.json": "./package.json"
|
|
37
41
|
},
|
|
38
42
|
"publishConfig": {
|
|
@@ -81,7 +85,13 @@
|
|
|
81
85
|
"@orpc/tanstack-query": "^2.0.0-beta.35",
|
|
82
86
|
"@tanstack/vue-query": "^5.102.8",
|
|
83
87
|
"nuxt": "^3.14.1592 || ^4.0.1",
|
|
88
|
+
"vitest": "^4.0.0 || ^5.0.0",
|
|
84
89
|
"vue": "^3.5.0"
|
|
85
90
|
},
|
|
91
|
+
"peerDependenciesMeta": {
|
|
92
|
+
"vitest": {
|
|
93
|
+
"optional": true
|
|
94
|
+
}
|
|
95
|
+
},
|
|
86
96
|
"packageManager": "bun@1.3.14"
|
|
87
97
|
}
|