@dimina-kit/inspect 0.4.0-dev.20260716153350 → 0.4.0-dev.20260716214527
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/dist/appdata-panel-view.js +1 -1
- package/dist/panel-view.js +8 -3
- package/package.json +1 -1
- package/src/appdata-panel-view-empty-state.test.tsx +19 -0
- package/src/appdata-panel-view.tsx +1 -1
- package/src/compile-source.ts +4 -1
- package/src/panel-view-root-expand.test.tsx +53 -0
- package/src/panel-view.tsx +7 -3
|
@@ -59,6 +59,6 @@ export function AppDataPanel({ state, onSelectBridge, isRuntimeRunning = true, }
|
|
|
59
59
|
const isActive = b.id === activeBridgeId;
|
|
60
60
|
const bridgeEntries = entries[b.id] ?? {};
|
|
61
61
|
const keys = Object.keys(bridgeEntries);
|
|
62
|
-
return (_jsx("div", { "data-bridge-id": b.id, className: "absolute inset-0 flex flex-col gap-2 p-2 overflow-y-auto", style: { display: isActive ? 'flex' : 'none' }, children: keys.length === 0 ? (_jsx("div", { className: "text-[12px] text-text-dim text-center px-4 py-6", children:
|
|
62
|
+
return (_jsx("div", { "data-bridge-id": b.id, className: "absolute inset-0 flex flex-col gap-2 p-2 overflow-y-auto", style: { display: isActive ? 'flex' : 'none' }, children: keys.length === 0 ? (_jsx("div", { className: "text-[12px] text-text-dim text-center px-4 py-6", children: isRuntimeRunning ? '暂无页面数据(仅显示 Page 级 data)' : '小程序未运行' })) : (keys.map((comp) => (_jsxs("div", { className: "border border-border-subtle rounded overflow-hidden shrink-0", children: [_jsx("div", { className: "bg-surface-3 px-2 py-0.5 text-[11px] text-code-label truncate", children: comp }), _jsx(JsonView, { value: (bridgeEntries[comp] ?? {}), collapsed: 1, displayDataTypes: false, displayObjectSize: false, enableClipboard: false, indentWidth: 12, style: JSON_VIEW_STYLE })] }, `${b.id}::${comp}`)))) }, b.id));
|
|
63
63
|
}) }))] }));
|
|
64
64
|
}
|
package/dist/panel-view.js
CHANGED
|
@@ -6,7 +6,12 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
|
|
6
6
|
* - 路径式 tag(含 `/`)= 页面或自定义组件,默认展开(让用户一眼看清组件层级)
|
|
7
7
|
* - 普通 DOM 节点(view/text 等)默认折叠,需要用户手动点开
|
|
8
8
|
*/
|
|
9
|
-
function isDefaultExpanded(node) {
|
|
9
|
+
function isDefaultExpanded(node, depth) {
|
|
10
|
+
// The root always opens expanded: collapsed, it hides the entire tree
|
|
11
|
+
// behind one ▸ row and the panel reads as blank — regardless of what tag
|
|
12
|
+
// shape the runtime gives the root (plain `page`, a component path, …).
|
|
13
|
+
if (depth === 0)
|
|
14
|
+
return true;
|
|
10
15
|
if (node.tagName === '#shadow-root')
|
|
11
16
|
return true;
|
|
12
17
|
if (node.tagName.includes('/'))
|
|
@@ -28,7 +33,7 @@ function WxmlFragmentNode({ node, depth, inspectedSid, onInspect }) {
|
|
|
28
33
|
// Shadow root — synthetic boundary for custom component internals.
|
|
29
34
|
// Clickable to collapse like WeChat DevTools; default expanded; no closing tag.
|
|
30
35
|
function WxmlShadowRootNode({ node, depth, inspectedSid, onInspect }) {
|
|
31
|
-
const [expanded, setExpanded] = useState(() => isDefaultExpanded(node));
|
|
36
|
+
const [expanded, setExpanded] = useState(() => isDefaultExpanded(node, depth));
|
|
32
37
|
const indent = depth * 16;
|
|
33
38
|
const hasShadowChildren = (node.children ?? []).length > 0;
|
|
34
39
|
return (_jsxs("div", { children: [_jsxs("div", { className: "py-px leading-[18px] hover:bg-surface-2 cursor-pointer", style: { paddingLeft: indent }, onClick: () => hasShadowChildren && setExpanded(!expanded), children: [_jsx("span", { className: "text-text-dim w-3 shrink-0 inline-block text-center select-none", children: hasShadowChildren ? (expanded ? '▾' : '▸') : ' ' }), _jsx("span", { className: "text-text-dim italic", children: "#shadow-root" })] }), expanded && node.children.map((child, i) => (_jsx(WxmlTreeNode, { node: child, depth: depth + 1, inspectedSid: inspectedSid, onInspect: onInspect }, i)))] }));
|
|
@@ -47,7 +52,7 @@ function WxmlBlockRow({ node, depth, indent, attrEntries, hasChildren, expanded,
|
|
|
47
52
|
}
|
|
48
53
|
// Ordinary element node — decides between the inline-text and block layouts.
|
|
49
54
|
function WxmlElementNode({ node, depth, inspectedSid, onInspect }) {
|
|
50
|
-
const [expanded, setExpanded] = useState(() => isDefaultExpanded(node));
|
|
55
|
+
const [expanded, setExpanded] = useState(() => isDefaultExpanded(node, depth));
|
|
51
56
|
const indent = depth * 16;
|
|
52
57
|
const isInspected = Boolean(node.sid && node.sid === inspectedSid);
|
|
53
58
|
const hasChildren = (node.children ?? []).length > 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dimina-kit/inspect",
|
|
3
|
-
"version": "0.4.0-dev.
|
|
3
|
+
"version": "0.4.0-dev.20260716214527",
|
|
4
4
|
"description": "Host-agnostic WXML tree extraction and inspection for dimina render layers: Vue runtime walk, stable-id registry, mutation-observing inspector, and a React tree panel.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dimina",
|
|
@@ -34,6 +34,25 @@ describe('AppDataPanel: empty-state copy reflects whether the session is actuall
|
|
|
34
34
|
).toBe(true)
|
|
35
35
|
})
|
|
36
36
|
|
|
37
|
+
it('keeps the "not running" copy inside a kept-alive bridge container whose entries are empty', () => {
|
|
38
|
+
// A host-built snapshot can carry a bridge with zero entries; the
|
|
39
|
+
// per-bridge empty text must not claim "no page data yet" while the
|
|
40
|
+
// session is actually down.
|
|
41
|
+
const state: AppDataState = {
|
|
42
|
+
bridges: [{ id: 'b1', pagePath: 'pages/index/index' }],
|
|
43
|
+
activeBridgeId: 'b1',
|
|
44
|
+
entries: { b1: {} },
|
|
45
|
+
}
|
|
46
|
+
const { getByTestId } = render(
|
|
47
|
+
<AppDataPanel state={state} onSelectBridge={() => {}} isRuntimeRunning={false} />,
|
|
48
|
+
)
|
|
49
|
+
const text = getByTestId('appdata-panel').textContent ?? ''
|
|
50
|
+
expect(
|
|
51
|
+
text.includes('未运行'),
|
|
52
|
+
`expected the per-bridge empty state to signal the session is not running; got: "${text}"`,
|
|
53
|
+
).toBe(true)
|
|
54
|
+
})
|
|
55
|
+
|
|
37
56
|
it('keeps the existing "暂无页面数据" copy (unchanged) once running', () => {
|
|
38
57
|
const { getByTestId, getByText } = render(<AppDataPanel {...makeProps(true)} />)
|
|
39
58
|
expect(getByText('暂无页面数据(仅显示 Page 级 data)')).toBeTruthy()
|
|
@@ -132,7 +132,7 @@ export function AppDataPanel({
|
|
|
132
132
|
>
|
|
133
133
|
{keys.length === 0 ? (
|
|
134
134
|
<div className="text-[12px] text-text-dim text-center px-4 py-6">
|
|
135
|
-
暂无页面数据(仅显示 Page 级 data)
|
|
135
|
+
{isRuntimeRunning ? '暂无页面数据(仅显示 Page 级 data)' : '小程序未运行'}
|
|
136
136
|
</div>
|
|
137
137
|
) : (
|
|
138
138
|
keys.map((comp) => (
|
package/src/compile-source.ts
CHANGED
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
// in-page compile-service callback, or anything else.
|
|
6
6
|
import type { CompileEvent, CompileLogEntry } from './compile-types.js'
|
|
7
7
|
|
|
8
|
-
/** The seed payload: both stores, chronological (oldest first).
|
|
8
|
+
/** The seed payload: both stores, chronological (oldest first). Cross-stream
|
|
9
|
+
* order between an event and a log sharing the same millisecond `at` is only
|
|
10
|
+
* preserved when the host stamps `seq` itself — an unstamped snapshot gets
|
|
11
|
+
* `seq` assigned per store (events first), so such ties sort event-first. */
|
|
9
12
|
export interface CompileFeedSnapshot {
|
|
10
13
|
events: CompileEvent[]
|
|
11
14
|
logs: CompileLogEntry[]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WxmlPanel root-expansion contract: the tree's root node renders expanded by
|
|
3
|
+
* default. A collapsed root hides the ENTIRE tree behind a single ▸ row, so
|
|
4
|
+
* the panel would open showing no structure at all — the first level must be
|
|
5
|
+
* visible without a manual expand click. Guards against expand heuristics
|
|
6
|
+
* that key off tag shape (e.g. component-path tags) and silently stop
|
|
7
|
+
* matching the root when the runtime's tag naming changes.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, expect, it } from 'vitest'
|
|
10
|
+
import { render } from '@testing-library/react'
|
|
11
|
+
import { WxmlPanel } from './panel-view.js'
|
|
12
|
+
import type { WxmlNode } from './types.js'
|
|
13
|
+
|
|
14
|
+
const TREE: WxmlNode = {
|
|
15
|
+
tagName: 'page',
|
|
16
|
+
attrs: {},
|
|
17
|
+
children: [
|
|
18
|
+
{ tagName: 'view', attrs: { class: 'container' }, children: [] },
|
|
19
|
+
],
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('WxmlPanel: root renders expanded by default', () => {
|
|
23
|
+
it('shows the root children without a manual expand click', () => {
|
|
24
|
+
const { container } = render(<WxmlPanel tree={TREE} />)
|
|
25
|
+
const text = container.textContent ?? ''
|
|
26
|
+
expect(text).toContain('page')
|
|
27
|
+
expect(
|
|
28
|
+
text,
|
|
29
|
+
'the first tree level must be visible on open — a collapsed root leaves the panel blank',
|
|
30
|
+
).toContain('container')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('keeps non-root plain-tag nodes collapsed by default', () => {
|
|
34
|
+
const nested: WxmlNode = {
|
|
35
|
+
tagName: 'page',
|
|
36
|
+
attrs: {},
|
|
37
|
+
children: [
|
|
38
|
+
{
|
|
39
|
+
tagName: 'view',
|
|
40
|
+
attrs: { class: 'outer' },
|
|
41
|
+
children: [{ tagName: 'text', attrs: { class: 'inner-marker' }, children: [] }],
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
}
|
|
45
|
+
const { container } = render(<WxmlPanel tree={nested} />)
|
|
46
|
+
const text = container.textContent ?? ''
|
|
47
|
+
expect(text).toContain('outer')
|
|
48
|
+
expect(
|
|
49
|
+
text,
|
|
50
|
+
'depth-1 plain tags keep the collapsed default — root expansion must not cascade',
|
|
51
|
+
).not.toContain('inner-marker')
|
|
52
|
+
})
|
|
53
|
+
})
|
package/src/panel-view.tsx
CHANGED
|
@@ -22,7 +22,11 @@ interface WxmlTreeNodeProps {
|
|
|
22
22
|
* - 路径式 tag(含 `/`)= 页面或自定义组件,默认展开(让用户一眼看清组件层级)
|
|
23
23
|
* - 普通 DOM 节点(view/text 等)默认折叠,需要用户手动点开
|
|
24
24
|
*/
|
|
25
|
-
function isDefaultExpanded(node: WxmlNode): boolean {
|
|
25
|
+
function isDefaultExpanded(node: WxmlNode, depth: number): boolean {
|
|
26
|
+
// The root always opens expanded: collapsed, it hides the entire tree
|
|
27
|
+
// behind one ▸ row and the panel reads as blank — regardless of what tag
|
|
28
|
+
// shape the runtime gives the root (plain `page`, a component path, …).
|
|
29
|
+
if (depth === 0) return true
|
|
26
30
|
if (node.tagName === '#shadow-root') return true
|
|
27
31
|
if (node.tagName.includes('/')) return true
|
|
28
32
|
return false
|
|
@@ -77,7 +81,7 @@ function WxmlFragmentNode({ node, depth, inspectedSid, onInspect }: WxmlTreeNode
|
|
|
77
81
|
// Shadow root — synthetic boundary for custom component internals.
|
|
78
82
|
// Clickable to collapse like WeChat DevTools; default expanded; no closing tag.
|
|
79
83
|
function WxmlShadowRootNode({ node, depth, inspectedSid, onInspect }: WxmlTreeNodeProps) {
|
|
80
|
-
const [expanded, setExpanded] = useState(() => isDefaultExpanded(node))
|
|
84
|
+
const [expanded, setExpanded] = useState(() => isDefaultExpanded(node, depth))
|
|
81
85
|
const indent = depth * 16
|
|
82
86
|
const hasShadowChildren = (node.children ?? []).length > 0
|
|
83
87
|
return (
|
|
@@ -206,7 +210,7 @@ function WxmlBlockRow({
|
|
|
206
210
|
|
|
207
211
|
// Ordinary element node — decides between the inline-text and block layouts.
|
|
208
212
|
function WxmlElementNode({ node, depth, inspectedSid, onInspect }: WxmlTreeNodeProps) {
|
|
209
|
-
const [expanded, setExpanded] = useState(() => isDefaultExpanded(node))
|
|
213
|
+
const [expanded, setExpanded] = useState(() => isDefaultExpanded(node, depth))
|
|
210
214
|
const indent = depth * 16
|
|
211
215
|
const isInspected = Boolean(node.sid && node.sid === inspectedSid)
|
|
212
216
|
const hasChildren = (node.children ?? []).length > 0
|