@lark-apaas/client-toolkit 1.2.70-alpha.20260909115935 → 1.2.70-beta.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.
|
@@ -1,36 +1,41 @@
|
|
|
1
1
|
import { Fragment, jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect } from "react";
|
|
3
|
-
import { snapdom } from "@zumer/snapdom";
|
|
4
|
-
import { submitPostMessage } from "../../utils/postMessage.js";
|
|
5
3
|
import { clearNetworkIdle, networkIdleCallback } from "network-idle";
|
|
4
|
+
import { submitPostMessage } from "../../utils/postMessage.js";
|
|
6
5
|
import { normalizeBasePath } from "../../utils/utils.js";
|
|
7
|
-
function PageHoc(
|
|
8
|
-
const { children } = props;
|
|
6
|
+
function PageHoc({ children }) {
|
|
9
7
|
useEffect(()=>{
|
|
10
|
-
if ('
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
if ('development' !== process.env.NODE_ENV) return;
|
|
9
|
+
let cancelled = false;
|
|
10
|
+
const basePath = normalizeBasePath(process.env.CLIENT_BASE_PATH);
|
|
11
|
+
const id = networkIdleCallback(()=>{
|
|
12
|
+
if (cancelled) return;
|
|
13
|
+
(async ()=>{
|
|
14
|
+
const { snapdom } = await import("@zumer/snapdom");
|
|
15
|
+
if (cancelled) return;
|
|
16
|
+
const result = await snapdom(document.body, {
|
|
16
17
|
format: 'png',
|
|
17
|
-
width,
|
|
18
|
-
height,
|
|
19
|
-
fallbackURL: ({ src })=>`${basePath}/dev/snapdom-proxy?url=${encodeURIComponent(src)}`,
|
|
18
|
+
width: document.body.scrollWidth,
|
|
19
|
+
height: document.body.scrollHeight,
|
|
20
|
+
fallbackURL: ({ src = '' })=>`${basePath}/dev/snapdom-proxy?url=${encodeURIComponent(src)}`,
|
|
20
21
|
exclude: [
|
|
21
22
|
'#lucide-react-svg'
|
|
22
23
|
],
|
|
23
24
|
backgroundColor: '#ffffff'
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
25
|
+
});
|
|
26
|
+
if (cancelled) return;
|
|
27
|
+
const image = await result.toPng();
|
|
28
|
+
if (cancelled) return;
|
|
29
|
+
submitPostMessage({
|
|
30
|
+
type: 'PageScreenshot',
|
|
31
|
+
data: image.src
|
|
32
|
+
});
|
|
33
|
+
})().catch(()=>{});
|
|
34
|
+
}, 2000);
|
|
35
|
+
return ()=>{
|
|
36
|
+
cancelled = true;
|
|
37
|
+
clearNetworkIdle(id);
|
|
38
|
+
};
|
|
34
39
|
}, []);
|
|
35
40
|
return /*#__PURE__*/ jsx(Fragment, {
|
|
36
41
|
children: children
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { StrictMode, act } from "react";
|
|
3
|
+
import { createRoot } from "react-dom/client";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { PageHoc } from "../PageHoc.js";
|
|
6
|
+
import { snapdom } from "@zumer/snapdom";
|
|
7
|
+
import { clearNetworkIdle, networkIdleCallback } from "network-idle";
|
|
8
|
+
import { submitPostMessage } from "../../../utils/postMessage.js";
|
|
9
|
+
vi.mock('@zumer/snapdom', ()=>({
|
|
10
|
+
snapdom: vi.fn()
|
|
11
|
+
}));
|
|
12
|
+
vi.mock('network-idle', ()=>({
|
|
13
|
+
networkIdleCallback: vi.fn(),
|
|
14
|
+
clearNetworkIdle: vi.fn()
|
|
15
|
+
}));
|
|
16
|
+
vi.mock('../../../utils/postMessage', ()=>({
|
|
17
|
+
submitPostMessage: vi.fn()
|
|
18
|
+
}));
|
|
19
|
+
function Page() {
|
|
20
|
+
return /*#__PURE__*/ jsx(PageHoc, {
|
|
21
|
+
children: /*#__PURE__*/ jsx("div", {
|
|
22
|
+
children: "Application"
|
|
23
|
+
})
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
let root;
|
|
27
|
+
let container;
|
|
28
|
+
const png = 'data:image/png;base64,c2NyZWVuc2hvdA==';
|
|
29
|
+
async function mount(strict = false) {
|
|
30
|
+
await act(async ()=>{
|
|
31
|
+
root.render(strict ? /*#__PURE__*/ jsx(StrictMode, {
|
|
32
|
+
children: /*#__PURE__*/ jsx(Page, {})
|
|
33
|
+
}) : /*#__PURE__*/ jsx(Page, {}));
|
|
34
|
+
});
|
|
35
|
+
await act(async ()=>{
|
|
36
|
+
await vi.dynamicImportSettled();
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
async function idle() {
|
|
40
|
+
await act(async ()=>{
|
|
41
|
+
await vi.advanceTimersByTimeAsync(2000);
|
|
42
|
+
await vi.dynamicImportSettled();
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
beforeEach(()=>{
|
|
46
|
+
vi.useFakeTimers();
|
|
47
|
+
vi.clearAllMocks();
|
|
48
|
+
vi.stubEnv('NODE_ENV', 'development');
|
|
49
|
+
vi.stubEnv('CLIENT_BASE_PATH', '/app/test/');
|
|
50
|
+
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
51
|
+
container = document.createElement('div');
|
|
52
|
+
document.body.appendChild(container);
|
|
53
|
+
root = createRoot(container);
|
|
54
|
+
vi.mocked(networkIdleCallback).mockImplementation((callback, delay)=>window.setTimeout(callback, delay));
|
|
55
|
+
vi.mocked(clearNetworkIdle).mockImplementation((id)=>window.clearTimeout(id));
|
|
56
|
+
vi.mocked(snapdom).mockResolvedValue({
|
|
57
|
+
toPng: vi.fn().mockResolvedValue({
|
|
58
|
+
src: png
|
|
59
|
+
})
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
afterEach(async ()=>{
|
|
63
|
+
await act(async ()=>root.unmount());
|
|
64
|
+
container.remove();
|
|
65
|
+
vi.useRealTimers();
|
|
66
|
+
vi.unstubAllEnvs();
|
|
67
|
+
});
|
|
68
|
+
describe('development page screenshot', ()=>{
|
|
69
|
+
it('captures after idle and reports the PNG with the base-path image fallback', async ()=>{
|
|
70
|
+
await mount();
|
|
71
|
+
expect(snapdom).not.toHaveBeenCalled();
|
|
72
|
+
expect(networkIdleCallback).toHaveBeenCalledWith(expect.any(Function), 2000);
|
|
73
|
+
await idle();
|
|
74
|
+
expect(snapdom).toHaveBeenCalledWith(document.body, expect.objectContaining({
|
|
75
|
+
format: 'png',
|
|
76
|
+
backgroundColor: '#ffffff',
|
|
77
|
+
exclude: [
|
|
78
|
+
'#lucide-react-svg'
|
|
79
|
+
]
|
|
80
|
+
}));
|
|
81
|
+
const options = vi.mocked(snapdom).mock.calls[0][1];
|
|
82
|
+
const fallback = options?.fallbackURL;
|
|
83
|
+
expect(fallback({
|
|
84
|
+
src: 'https://example.com/image.png?a=1&b=2'
|
|
85
|
+
})).toBe('/app/test/dev/snapdom-proxy?url=https%3A%2F%2Fexample.com%2Fimage.png%3Fa%3D1%26b%3D2');
|
|
86
|
+
expect(submitPostMessage).toHaveBeenCalledWith({
|
|
87
|
+
type: 'PageScreenshot',
|
|
88
|
+
data: png
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
it.each([
|
|
92
|
+
'production',
|
|
93
|
+
'test',
|
|
94
|
+
'staging',
|
|
95
|
+
'',
|
|
96
|
+
void 0
|
|
97
|
+
])('does not schedule or capture when NODE_ENV is %s', async (env)=>{
|
|
98
|
+
vi.stubEnv('NODE_ENV', env);
|
|
99
|
+
await mount();
|
|
100
|
+
await idle();
|
|
101
|
+
expect(networkIdleCallback).not.toHaveBeenCalled();
|
|
102
|
+
expect(snapdom).not.toHaveBeenCalled();
|
|
103
|
+
expect(submitPostMessage).not.toHaveBeenCalled();
|
|
104
|
+
});
|
|
105
|
+
it('only schedules one active capture under StrictMode', async ()=>{
|
|
106
|
+
await mount(true);
|
|
107
|
+
await idle();
|
|
108
|
+
expect(snapdom).toHaveBeenCalledTimes(1);
|
|
109
|
+
expect(submitPostMessage).toHaveBeenCalledTimes(1);
|
|
110
|
+
});
|
|
111
|
+
it('clears the idle callback on unmount', async ()=>{
|
|
112
|
+
await mount();
|
|
113
|
+
await act(async ()=>root.render(null));
|
|
114
|
+
await idle();
|
|
115
|
+
expect(clearNetworkIdle).toHaveBeenCalledTimes(1);
|
|
116
|
+
expect(snapdom).not.toHaveBeenCalled();
|
|
117
|
+
});
|
|
118
|
+
it('discards a PNG that finishes after unmount', async ()=>{
|
|
119
|
+
let finish;
|
|
120
|
+
const pending = new Promise((resolve)=>{
|
|
121
|
+
finish = resolve;
|
|
122
|
+
});
|
|
123
|
+
vi.mocked(snapdom).mockResolvedValue({
|
|
124
|
+
toPng: ()=>pending
|
|
125
|
+
});
|
|
126
|
+
await mount();
|
|
127
|
+
await idle();
|
|
128
|
+
await act(async ()=>root.render(null));
|
|
129
|
+
await act(async ()=>finish({
|
|
130
|
+
src: png
|
|
131
|
+
}));
|
|
132
|
+
expect(submitPostMessage).not.toHaveBeenCalled();
|
|
133
|
+
});
|
|
134
|
+
it('keeps the application mounted when capture fails', async ()=>{
|
|
135
|
+
vi.mocked(snapdom).mockRejectedValue(new Error('image unavailable'));
|
|
136
|
+
await mount();
|
|
137
|
+
await idle();
|
|
138
|
+
expect(container.textContent).toBe('Application');
|
|
139
|
+
expect(submitPostMessage).not.toHaveBeenCalled();
|
|
140
|
+
});
|
|
141
|
+
});
|