@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,4 +1,5 @@
1
- import React, { ReactNode } from 'react';
2
- export declare function PageHoc(props: {
1
+ import React, { type ReactNode } from 'react';
2
+ /** 仅开发态生成首屏封面,截图失败不影响页面。 */
3
+ export declare function PageHoc({ children }: {
3
4
  children: ReactNode;
4
5
  }): React.JSX.Element;
@@ -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(props) {
8
- const { children } = props;
6
+ function PageHoc({ children }) {
9
7
  useEffect(()=>{
10
- if ('production' !== process.env.NODE_ENV) {
11
- const basePath = normalizeBasePath(process.env.CLIENT_BASE_PATH);
12
- const id = networkIdleCallback(()=>{
13
- const width = document.body.scrollWidth;
14
- const height = document.body.scrollHeight;
15
- snapdom(document.body, {
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
- }).then(async (res)=>{
25
- const imgEle = await res.toPng();
26
- submitPostMessage({
27
- type: 'PageScreenshot',
28
- data: imgEle.src
29
- });
30
- }).catch(()=>{});
31
- }, 2000);
32
- return ()=>clearNetworkIdle(id);
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,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
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/client-toolkit",
3
- "version": "1.2.70-alpha.20260909115935",
3
+ "version": "1.2.70-beta.0",
4
4
  "types": "./lib/index.d.ts",
5
5
  "main": "./lib/index.js",
6
6
  "files": [