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 CHANGED
@@ -432,66 +432,77 @@ const orpc = createORPCNuxtClient(client, { prefix: "blog" })
432
432
 
433
433
  ### Component tests
434
434
 
435
- Component tests need the Nuxt environment of `@nuxt/test-utils`:
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
- Replace `useOrpc()` with a client that returns fixed data, then mount the component with `mountSuspended()` so its awaited queries resolve before the assertions.
446
- The replacement can be any nested object of async functions; only the procedures the component calls have to exist.
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/post-list.spec.ts
450
- import { mockNuxtImport, mountSuspended } from "@nuxt/test-utils/runtime"
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 { createORPCNuxtClient } from "orpc-nuxt/client"
453
- import { afterEach, expect, test } from "vitest"
458
+ import { createTestORPCClient } from "orpc-nuxt/testing"
459
+ import { afterEach } from "vitest"
454
460
 
455
- import PostList from "~/components/post-list.vue"
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 orpc = createORPCNuxtClient(
463
- {
464
- blog: {
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 runs this factory before the file body, so it must return the composable without calling it.
474
- mockNuxtImport("useOrpc", () => () => orpc)
472
+ // Return the composable itself; Vitest hoists this factory before the setup file runs.
473
+ mockNuxtImport("useOrpc", () => () => client)
475
474
 
476
475
  afterEach(() => {
477
- // Start every test from an empty cache, so an earlier response cannot satisfy a later query.
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
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": "^3.14.1592 || ^4.0.1"
6
6
  },
7
- "version": "0.2.0",
7
+ "version": "0.3.0",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.3",
10
10
  "unbuild": "3.6.1"
@@ -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.2.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
  }