orpc-nuxt 0.2.0 → 0.3.1
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 +37 -38
- package/dist/module.json +1 -1
- package/dist/runtime/testing.d.ts +40 -0
- package/dist/runtime/testing.js +71 -0
- package/package.json +11 -1
package/README.md
CHANGED
|
@@ -432,66 +432,65 @@ 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
|
|
451
|
-
import {
|
|
452
|
-
import {
|
|
453
|
-
import { afterEach
|
|
454
|
+
// test/nuxt/setup.ts
|
|
455
|
+
import { mockNuxtImport } from "@nuxt/test-utils/runtime"
|
|
456
|
+
import type { RouterClient } from "@orpc/server"
|
|
457
|
+
import { createTestORPCClient } from "orpc-nuxt/testing"
|
|
458
|
+
import { afterEach } from "vitest"
|
|
454
459
|
|
|
455
|
-
import
|
|
460
|
+
import type { router } from "~~/server/rpc/router"
|
|
456
461
|
|
|
457
|
-
const
|
|
458
|
-
// Report a failing procedure instead of retrying it until the test times out.
|
|
459
|
-
defaultOptions: { queries: { retry: false } },
|
|
460
|
-
})
|
|
462
|
+
export const { client, procedures, reset } = createTestORPCClient<RouterClient<typeof router>>()
|
|
461
463
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
blog: {
|
|
465
|
-
posts: {
|
|
466
|
-
list: async () => [{ id: 1, title: "First post" }],
|
|
467
|
-
},
|
|
468
|
-
},
|
|
469
|
-
},
|
|
470
|
-
{ queryClient },
|
|
471
|
-
)
|
|
464
|
+
// Return the composable itself; Vitest hoists this factory before the setup file runs.
|
|
465
|
+
mockNuxtImport("useOrpc", () => () => client)
|
|
472
466
|
|
|
473
|
-
|
|
474
|
-
|
|
467
|
+
afterEach(reset)
|
|
468
|
+
```
|
|
475
469
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
470
|
+
`orpc-nuxt/testing` does not import `nuxt/app`, so the hoisted `mockNuxtImport()` factory can load it safely.
|
|
471
|
+
The client owns a QueryClient that never retries, so a failing procedure fails the test instead of timing out, and `reset()` removes the registered handlers together with the cached responses.
|
|
472
|
+
Reach that cache as `queryClient` to seed or inspect it, or pass your own to `createTestORPCClient()`; `reset()` clears that one as well.
|
|
473
|
+
|
|
474
|
+
Register the required handlers before mounting; `mountSuspended()` waits for awaited queries before assertions:
|
|
475
|
+
|
|
476
|
+
```ts
|
|
477
|
+
// test/nuxt/post-list.spec.ts
|
|
478
|
+
import { mountSuspended } from "@nuxt/test-utils/runtime"
|
|
479
|
+
import { expect, test } from "vitest"
|
|
480
|
+
|
|
481
|
+
import PostList from "~/components/post-list.vue"
|
|
482
|
+
|
|
483
|
+
import { procedures } from "./setup"
|
|
480
484
|
|
|
481
485
|
test("renders the posts", async () => {
|
|
486
|
+
const list = procedures.blog.posts.list.handle(() => [{ id: 1, title: "First post" }])
|
|
482
487
|
const component = await mountSuspended(PostList)
|
|
488
|
+
|
|
483
489
|
expect(component.text()).toContain("First post")
|
|
490
|
+
expect(list).toHaveBeenCalledOnce()
|
|
484
491
|
})
|
|
485
492
|
```
|
|
486
493
|
|
|
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
494
|
### Outside Vue components
|
|
496
495
|
|
|
497
496
|
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,40 @@
|
|
|
1
|
+
import type { AnyNestedClient, Client } from "@orpc/client";
|
|
2
|
+
import { QueryClient } from "@tanstack/vue-query";
|
|
3
|
+
import type { Mock } from "vitest";
|
|
4
|
+
import type { ORPCNuxtClient, ORPCNuxtClientOptions } from "./types.js";
|
|
5
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
6
|
+
/** A procedure implementation registered for a test client. */
|
|
7
|
+
export type TestORPCHandler<TProcedure> = TProcedure extends Client<infer _Context, infer Input, infer Output, infer _Error> ? (input: Input) => MaybePromise<Awaited<Output>> : never;
|
|
8
|
+
/** Registration methods for one procedure in a test client. */
|
|
9
|
+
export interface TestORPCProcedure<TProcedure> {
|
|
10
|
+
/** Register the implementation used by subsequent calls and return its Vitest mock. */
|
|
11
|
+
handle(handler: TestORPCHandler<TProcedure>): Mock<TestORPCHandler<TProcedure>>;
|
|
12
|
+
}
|
|
13
|
+
/** A router-shaped tree whose procedure leaves register test implementations. */
|
|
14
|
+
export type TestORPCProcedures<T extends AnyNestedClient> = T extends Client<infer Context, infer Input, infer Output, infer Error> ? TestORPCProcedure<Client<Context, Input, Output, Error>> : {
|
|
15
|
+
[Key in keyof T]: T[Key] extends AnyNestedClient ? TestORPCProcedures<T[Key]> : never;
|
|
16
|
+
};
|
|
17
|
+
/** The isolated client, procedure registry and cleanup function created for a test suite. */
|
|
18
|
+
export interface TestORPCClient<T extends AnyNestedClient> {
|
|
19
|
+
/** The decorated client to return from a mocked `useOrpc()`. */
|
|
20
|
+
client: ORPCNuxtClient<T>;
|
|
21
|
+
/** Register typed implementations and receive Vitest mocks for call assertions. */
|
|
22
|
+
procedures: TestORPCProcedures<T>;
|
|
23
|
+
/** The cache the client reads and writes, to seed or inspect from a test. */
|
|
24
|
+
queryClient: QueryClient;
|
|
25
|
+
/** Remove every registered procedure implementation and clear the query cache. */
|
|
26
|
+
reset: () => void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Create an isolated fake oRPC client for Nuxt component tests.
|
|
30
|
+
* The entrypoint has no Nuxt runtime dependency.
|
|
31
|
+
* A shared setup file can therefore import it when Vitest hoists `mockNuxtImport()`.
|
|
32
|
+
*
|
|
33
|
+
* Without a `queryClient` the client owns one that never retries, so a failing procedure fails
|
|
34
|
+
* the test instead of retrying until it times out.
|
|
35
|
+
*
|
|
36
|
+
* @param options - Cache key prefix and a QueryClient to use instead of the owned one.
|
|
37
|
+
* @returns A decorated client, its typed registration tree, its cache and a reset function.
|
|
38
|
+
*/
|
|
39
|
+
export declare function createTestORPCClient<T extends AnyNestedClient>(options?: ORPCNuxtClientOptions): TestORPCClient<T>;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createORPCClient } from "@orpc/client";
|
|
2
|
+
import { QueryClient } from "@tanstack/vue-query";
|
|
3
|
+
import { vi } from "vitest";
|
|
4
|
+
import { createORPCNuxtClient } from "./client/create.js";
|
|
5
|
+
export function createTestORPCClient(options = {}) {
|
|
6
|
+
const registrations = /* @__PURE__ */ new Map();
|
|
7
|
+
const queryClient = options.queryClient ?? new QueryClient({
|
|
8
|
+
defaultOptions: {
|
|
9
|
+
// Retries turn a failing procedure into a test timeout instead of a reported failure.
|
|
10
|
+
queries: { retry: false }
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
function registerHandler(path, handler) {
|
|
14
|
+
const mock = vi.fn(handler);
|
|
15
|
+
registrations.set(path, mock);
|
|
16
|
+
return mock;
|
|
17
|
+
}
|
|
18
|
+
async function callHandler(path, input) {
|
|
19
|
+
const handler = registrations.get(path);
|
|
20
|
+
if (!handler) {
|
|
21
|
+
throw new Error(`No test handler is registered for oRPC procedure "${path}"`);
|
|
22
|
+
}
|
|
23
|
+
return await handler(input);
|
|
24
|
+
}
|
|
25
|
+
const link = {
|
|
26
|
+
async call(path, input) {
|
|
27
|
+
return await callHandler(path.join("."), input);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const client = createORPCNuxtClient(createORPCClient(link), { ...options, queryClient });
|
|
31
|
+
const procedures = createRecursiveProxy([], (path, args) => {
|
|
32
|
+
if (path.at(-1) !== "handle") {
|
|
33
|
+
throw new Error(`Unknown test procedure call: ${path.join(".")}`);
|
|
34
|
+
}
|
|
35
|
+
const handler = args[0];
|
|
36
|
+
if (typeof handler !== "function") {
|
|
37
|
+
throw new TypeError(
|
|
38
|
+
`A test handler must be a function for oRPC procedure "${path.slice(0, -1).join(".")}"`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return registerHandler(path.slice(0, -1).join("."), handler);
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
client,
|
|
45
|
+
procedures,
|
|
46
|
+
queryClient,
|
|
47
|
+
reset: () => {
|
|
48
|
+
registrations.clear();
|
|
49
|
+
queryClient.clear();
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function createRecursiveProxy(path, call) {
|
|
54
|
+
const target = () => {
|
|
55
|
+
};
|
|
56
|
+
const children = /* @__PURE__ */ new Map();
|
|
57
|
+
return new Proxy(target, {
|
|
58
|
+
get(_, property, receiver) {
|
|
59
|
+
if (typeof property !== "string" || property === "then") {
|
|
60
|
+
return Reflect.get(target, property, receiver);
|
|
61
|
+
}
|
|
62
|
+
if (children.has(property)) return children.get(property);
|
|
63
|
+
const child = createRecursiveProxy([...path, property], call);
|
|
64
|
+
children.set(property, child);
|
|
65
|
+
return child;
|
|
66
|
+
},
|
|
67
|
+
apply(_, __, args) {
|
|
68
|
+
return call(path, args);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orpc-nuxt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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
|
}
|