Skip to content

Commit 6809480

Browse files
authored
fix(vue): DataClientPLugin only runs manager start/stop for app lifecycle (#3584)
1 parent 86cb961 commit 6809480

15 files changed

+1112
-158
lines changed

.changeset/dirty-regions-dance.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@data-client/vue': patch
3+
---
4+
5+
Only run manager start/stop for app lifecycle - not every component mount

jest.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const baseConfig = {
2323
'node_modules',
2424
'/__tests__',
2525
'DevtoolsManager',
26-
'packages/vue',
26+
'packages/vue/src/test',
2727
'packages/test',
2828
'packages/graphql',
2929
'packages/rest/src/next',

packages/vue/src/__tests__/__snapshots__/useSubscription.web.ts.snap

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import { Controller, initialState } from '@data-client/core';
2+
import type { State } from '@data-client/core';
3+
import {
4+
describe,
5+
it,
6+
expect,
7+
beforeEach,
8+
afterEach,
9+
jest,
10+
} from '@jest/globals';
11+
import { shallowRef } from 'vue';
12+
13+
import {
14+
StateKey,
15+
ControllerKey,
16+
FallbackStateRef,
17+
useController,
18+
injectState,
19+
} from '../context';
20+
21+
// Mock Vue's inject function
22+
const mockInjectMap: Map<symbol, any> = new Map();
23+
24+
jest.mock('vue', () => {
25+
const actual = jest.requireActual('vue') as any;
26+
return {
27+
...(actual || {}),
28+
inject: jest.fn((key: symbol, defaultValue: any) => {
29+
return mockInjectMap.get(key) ?? defaultValue;
30+
}),
31+
};
32+
});
33+
34+
describe('context', () => {
35+
let consoleErrorSpy: jest.SpiedFunction<typeof console.error>;
36+
const originalNodeEnv = process.env.NODE_ENV;
37+
38+
beforeEach(() => {
39+
mockInjectMap.clear();
40+
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
41+
});
42+
43+
afterEach(() => {
44+
consoleErrorSpy.mockRestore();
45+
process.env.NODE_ENV = originalNodeEnv;
46+
});
47+
48+
describe('FallbackStateRef', () => {
49+
it('should be a shallow ref with initial state', () => {
50+
expect(FallbackStateRef.value).toEqual(initialState);
51+
});
52+
53+
it('should be reactive', () => {
54+
const initialValue = FallbackStateRef.value;
55+
const newState: State<unknown> = {
56+
...initialState,
57+
endpoints: { test: { data: 'value' } } as any,
58+
};
59+
FallbackStateRef.value = newState;
60+
expect(FallbackStateRef.value).toEqual(newState);
61+
// Restore original state
62+
FallbackStateRef.value = initialValue;
63+
});
64+
});
65+
66+
describe('useController', () => {
67+
it('should return the injected controller when provided', () => {
68+
const mockController = new Controller();
69+
mockInjectMap.set(ControllerKey, mockController);
70+
71+
const result = useController();
72+
73+
expect(result).toBe(mockController);
74+
expect(consoleErrorSpy).not.toHaveBeenCalled();
75+
});
76+
77+
it('should return a new Controller when not provided', () => {
78+
mockInjectMap.set(ControllerKey, null);
79+
80+
const result = useController();
81+
82+
expect(result).toBeInstanceOf(Controller);
83+
});
84+
85+
it('should log error in development when controller is not provided', () => {
86+
process.env.NODE_ENV = 'development';
87+
mockInjectMap.set(ControllerKey, null);
88+
89+
useController();
90+
91+
expect(consoleErrorSpy).toHaveBeenCalledWith(
92+
expect.stringContaining(
93+
'It appears you are trying to use Reactive Data Client (Vue) without a provider.',
94+
),
95+
);
96+
expect(consoleErrorSpy).toHaveBeenCalledWith(
97+
expect.stringContaining(
98+
'https://dataclient.io/docs/getting-started/installation',
99+
),
100+
);
101+
});
102+
103+
it('should not log error in production when controller is not provided', () => {
104+
process.env.NODE_ENV = 'production';
105+
mockInjectMap.set(ControllerKey, null);
106+
107+
useController();
108+
109+
expect(consoleErrorSpy).not.toHaveBeenCalled();
110+
});
111+
112+
it('should handle undefined controller', () => {
113+
mockInjectMap.set(ControllerKey, undefined);
114+
115+
const result = useController();
116+
117+
expect(result).toBeInstanceOf(Controller);
118+
});
119+
});
120+
121+
describe('injectState', () => {
122+
it('should return the injected state when provided', () => {
123+
const mockState = shallowRef<State<unknown>>({
124+
...initialState,
125+
endpoints: { test: { data: 'value' } } as any,
126+
});
127+
mockInjectMap.set(StateKey, mockState);
128+
129+
const result = injectState();
130+
131+
expect(result).toBe(mockState);
132+
expect(consoleErrorSpy).not.toHaveBeenCalled();
133+
});
134+
135+
it('should return FallbackStateRef when not provided', () => {
136+
mockInjectMap.set(StateKey, null);
137+
138+
const result = injectState();
139+
140+
expect(result).toBe(FallbackStateRef);
141+
});
142+
143+
it('should log error in development when state is not provided', () => {
144+
process.env.NODE_ENV = 'development';
145+
mockInjectMap.set(StateKey, null);
146+
147+
injectState();
148+
149+
expect(consoleErrorSpy).toHaveBeenCalledWith(
150+
expect.stringContaining(
151+
'It appears you are trying to use Reactive Data Client (Vue) without a provider.',
152+
),
153+
);
154+
expect(consoleErrorSpy).toHaveBeenCalledWith(
155+
expect.stringContaining(
156+
'https://dataclient.io/docs/getting-started/installation',
157+
),
158+
);
159+
});
160+
161+
it('should not log error in production when state is not provided', () => {
162+
process.env.NODE_ENV = 'production';
163+
mockInjectMap.set(StateKey, null);
164+
165+
injectState();
166+
167+
expect(consoleErrorSpy).not.toHaveBeenCalled();
168+
});
169+
170+
it('should handle undefined state', () => {
171+
mockInjectMap.set(StateKey, undefined);
172+
173+
const result = injectState();
174+
175+
expect(result).toBe(FallbackStateRef);
176+
});
177+
178+
it('should return state ref that is reactive', () => {
179+
const mockState = shallowRef<State<unknown>>(initialState);
180+
mockInjectMap.set(StateKey, mockState);
181+
182+
const result = injectState();
183+
184+
expect(result.value).toEqual(initialState);
185+
186+
const newState: State<unknown> = {
187+
...initialState,
188+
endpoints: { test: { data: 'updated' } } as any,
189+
};
190+
mockState.value = newState;
191+
192+
expect(result.value).toEqual(newState);
193+
});
194+
});
195+
});

packages/vue/src/__tests__/integration-garbage-collection.web.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@ import { defineComponent, h } from 'vue';
44
import { Article, ArticleResource } from '../../../../__tests__/new';
55
import useQuery from '../consumers/useQuery';
66
import useSuspense from '../consumers/useSuspense';
7-
import {
8-
renderDataComposable,
9-
renderDataClient,
10-
} from '../test/renderDataClient';
7+
import { renderDataComposable, renderDataClient } from '../test';
118

129
const GC_INTERVAL = 100; // Use short interval for faster tests
1310

packages/vue/src/__tests__/useQuery.web.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
SecondUnion,
1313
} from '../../../../__tests__/new';
1414
import useQuery from '../consumers/useQuery';
15-
import { renderDataComposable } from '../test/renderDataClient';
15+
import { renderDataComposable } from '../test';
1616

1717
// Inline fixtures (duplicated from React tests to avoid cross-project imports)
1818
const payloadSlug = {

0 commit comments

Comments
 (0)