@raxium/vue 0.1.2 → 0.1.3

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,7 +1,7 @@
1
1
  import type { SlotsType, VNode } from 'vue';
2
2
  import type { SpinRenderProps } from '.';
3
3
  /**
4
- * TODO: rslib无法解析<script setup lang="tsx">的组件, 会解析为虚拟请求(?vue), 导致解析链断掉
4
+ * rslib无法解析<script setup lang="tsx">的组件, 会解析为虚拟请求(?vue), 导致解析链断掉
5
5
  * 继续使用bundle:false, 需要直接解析tsx文件
6
6
  */
7
7
  type Slots = {
@@ -1,6 +1,7 @@
1
1
  import { computed, createVNode, defineComponent, isVNode, provide } from "vue";
2
2
  import { ark } from "@ark-ui/vue/factory";
3
3
  import { clsx } from "@raxium/themes/utils";
4
+ import { isEmptyVNode } from "../../utils/index.js";
4
5
  import { LoaderCircle } from "lucide-vue-next";
5
6
  function _isSlot(s) {
6
7
  return 'function' == typeof s || '[object Object]' === Object.prototype.toString.call(s) && !isVNode(s);
@@ -12,7 +13,7 @@ const SpinProvider = defineComponent({
12
13
  function renderIcon(props) {
13
14
  const icon = slots?.icon?.(props);
14
15
  const crafts = computed(()=>props.theme?.crafts?.tvSpin?.());
15
- if (icon) return createVNode(ark.span, {
16
+ if (!isEmptyVNode(icon)) return createVNode(ark.span, {
16
17
  class: crafts.value?.icon({
17
18
  class: clsx(props.class),
18
19
  ...props.theme
@@ -0,0 +1,191 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { Comment, Fragment, Text, createCommentVNode, createTextVNode, defineComponent, h } from "vue";
3
+ import { checkContextVNodePosition, excludeVNodesByName, excludeVNodesByNames, findVNodeByName, findVNodesByName, hasChildVNodeByName, isEmptyVNode, someVNode } from "../vnode.js";
4
+ function cmp(name) {
5
+ return defineComponent({
6
+ name,
7
+ setup: ()=>()=>null
8
+ });
9
+ }
10
+ describe('someVNode', ()=>{
11
+ it('returns false for undefined / empty', ()=>{
12
+ expect(someVNode(void 0, ()=>true)).toBe(false);
13
+ expect(someVNode([], ()=>true)).toBe(false);
14
+ });
15
+ it('matches direct vnode', ()=>{
16
+ const n = h('div');
17
+ expect(someVNode(n, (v)=>'div' === v.type)).toBe(true);
18
+ expect(someVNode(n, (v)=>'span' === v.type)).toBe(false);
19
+ });
20
+ it('recurses into array children (e.g. Fragment)', ()=>{
21
+ const inner = h('span');
22
+ const frag = h(Fragment, null, [
23
+ inner
24
+ ]);
25
+ expect(someVNode(frag, (v)=>'span' === v.type)).toBe(true);
26
+ });
27
+ });
28
+ describe('findVNodeByName', ()=>{
29
+ it('finds component by name with camelCase normalization', ()=>{
30
+ const C = cmp('MyWidget');
31
+ const nodes = [
32
+ h('div'),
33
+ h(C)
34
+ ];
35
+ expect(findVNodeByName(nodes, 'my-widget')).toBeDefined();
36
+ expect(findVNodeByName(nodes, 'MyWidget')).toBeDefined();
37
+ expect(findVNodeByName(nodes, 'my-widget').type.name).toBe('MyWidget');
38
+ });
39
+ it('returns undefined when missing or nodes empty', ()=>{
40
+ expect(findVNodeByName(void 0, 'x')).toBeUndefined();
41
+ expect(findVNodeByName([], 'x')).toBeUndefined();
42
+ expect(findVNodeByName([
43
+ h('div')
44
+ ], 'Missing')).toBeUndefined();
45
+ });
46
+ it('walks Fragment children', ()=>{
47
+ const Inner = cmp('InnerNode');
48
+ const frag = h(Fragment, null, [
49
+ h(Inner)
50
+ ]);
51
+ expect(findVNodeByName([
52
+ frag
53
+ ], 'InnerNode')).toBeDefined();
54
+ });
55
+ });
56
+ describe('findVNodesByName', ()=>{
57
+ it('collects all matches', ()=>{
58
+ const A = cmp('Dup');
59
+ const nodes = [
60
+ h(A),
61
+ h('p'),
62
+ h(A)
63
+ ];
64
+ const found = findVNodesByName(nodes, 'Dup');
65
+ expect(found).toHaveLength(2);
66
+ });
67
+ });
68
+ describe('excludeVNodesByName / excludeVNodesByNames', ()=>{
69
+ it('excludes one name', ()=>{
70
+ const A = cmp('Keep');
71
+ const B = cmp('Drop');
72
+ const out = excludeVNodesByName([
73
+ h(A),
74
+ h(B),
75
+ h('i')
76
+ ], 'Drop');
77
+ expect(out).toHaveLength(2);
78
+ expect(out.some((v)=>v.type?.name === 'Drop')).toBe(false);
79
+ });
80
+ it('excludes multiple names', ()=>{
81
+ const A = cmp('A1');
82
+ const B = cmp('A2');
83
+ const out = excludeVNodesByNames([
84
+ h(A),
85
+ h(B),
86
+ h('span')
87
+ ], [
88
+ 'A1',
89
+ 'A2'
90
+ ]);
91
+ expect(out).toHaveLength(1);
92
+ expect(out[0].type).toBe('span');
93
+ });
94
+ it('returns [] for empty input', ()=>{
95
+ expect(excludeVNodesByName(void 0, 'x')).toEqual([]);
96
+ });
97
+ });
98
+ describe('hasChildVNodeByName', ()=>{
99
+ it('matches root component name', ()=>{
100
+ const C = cmp('RootX');
101
+ expect(hasChildVNodeByName(h(C), 'root-x')).toBe(true);
102
+ });
103
+ it('matches nested component in children array', ()=>{
104
+ const Inner = cmp('Nested');
105
+ const root = h('div', [
106
+ h(Inner)
107
+ ]);
108
+ expect(hasChildVNodeByName(root, 'Nested')).toBe(true);
109
+ });
110
+ it('returns false when absent', ()=>{
111
+ expect(hasChildVNodeByName(void 0, 'x')).toBe(false);
112
+ expect(hasChildVNodeByName(h('div'), 'None')).toBe(false);
113
+ });
114
+ it('handles vnode array at root', ()=>{
115
+ const Inner = cmp('InArray');
116
+ expect(hasChildVNodeByName([
117
+ h('div'),
118
+ h(Inner)
119
+ ], 'InArray')).toBe(true);
120
+ });
121
+ });
122
+ describe('checkContextVNodePosition', ()=>{
123
+ it('warns when single child name equals context', ()=>{
124
+ const warn = vi.spyOn(console, 'warn').mockImplementation(()=>{});
125
+ const C = cmp('ContextHost');
126
+ checkContextVNodePosition([
127
+ h(C)
128
+ ], 'ContextHost', 'Parent');
129
+ expect(warn).toHaveBeenCalledTimes(1);
130
+ warn.mockRestore();
131
+ });
132
+ it('does not warn when multiple nodes', ()=>{
133
+ const warn = vi.spyOn(console, 'warn').mockImplementation(()=>{});
134
+ const C = cmp('ContextHost');
135
+ checkContextVNodePosition([
136
+ h(C),
137
+ h('div')
138
+ ], 'ContextHost', 'Parent');
139
+ expect(warn).not.toHaveBeenCalled();
140
+ warn.mockRestore();
141
+ });
142
+ });
143
+ describe('isEmptyVNode', ()=>{
144
+ it('treats null / undefined as empty', ()=>{
145
+ expect(isEmptyVNode(null)).toBe(true);
146
+ expect(isEmptyVNode(void 0)).toBe(true);
147
+ });
148
+ it('treats Comment as empty', ()=>{
149
+ const v = createCommentVNode('x');
150
+ expect(v.type).toBe(Comment);
151
+ expect(isEmptyVNode(v)).toBe(true);
152
+ });
153
+ it('treats blank Text as empty', ()=>{
154
+ expect(isEmptyVNode(createTextVNode(''))).toBe(true);
155
+ expect(isEmptyVNode(createTextVNode(' '))).toBe(true);
156
+ });
157
+ it('treats non-blank Text as non-empty', ()=>{
158
+ expect(isEmptyVNode(createTextVNode('a'))).toBe(false);
159
+ });
160
+ it('treats empty Fragment as empty', ()=>{
161
+ expect(isEmptyVNode(h(Fragment, null, []))).toBe(true);
162
+ expect(isEmptyVNode(h(Fragment, null, null))).toBe(true);
163
+ });
164
+ it('treats Fragment with only empty children as empty', ()=>{
165
+ expect(isEmptyVNode(h(Fragment, null, [
166
+ createCommentVNode(),
167
+ createTextVNode('')
168
+ ]))).toBe(true);
169
+ });
170
+ it('treats element / component as non-empty', ()=>{
171
+ expect(isEmptyVNode(h('div'))).toBe(false);
172
+ expect(isEmptyVNode(h(cmp('X')))).toBe(false);
173
+ });
174
+ it('requires every item in array to be empty', ()=>{
175
+ expect(isEmptyVNode([
176
+ createCommentVNode(),
177
+ createTextVNode('')
178
+ ])).toBe(true);
179
+ expect(isEmptyVNode([
180
+ createCommentVNode(),
181
+ h('span')
182
+ ])).toBe(false);
183
+ });
184
+ it('treats non-VNode object as empty (compat)', ()=>{
185
+ expect(isEmptyVNode({})).toBe(true);
186
+ });
187
+ it('compares Text type symbol', ()=>{
188
+ const tv = createTextVNode('hi');
189
+ expect(tv.type).toBe(Text);
190
+ });
191
+ });
@@ -6,3 +6,4 @@ export declare function excludeVNodesByName(node: VNode | VNode[] | undefined, n
6
6
  export declare function excludeVNodesByNames(nodes: VNode[] | undefined, name: string[]): VNode[];
7
7
  export declare function hasChildVNodeByName(node: VNode | VNode[] | undefined, name: string): boolean;
8
8
  export declare function checkContextVNodePosition(nodes: VNode[] | undefined, contextName: string, componentName?: string): void;
9
+ export declare function isEmptyVNode(node: VNode | VNode[] | null | undefined): boolean;
@@ -1,5 +1,5 @@
1
1
  import { camelCase, isEmpty } from "es-toolkit/compat";
2
- import { isVNode } from "vue";
2
+ import { Comment, Fragment, Text, isVNode } from "vue";
3
3
  function someVNode(children, pred) {
4
4
  const arr = Array.isArray(children) ? children : null != children ? [
5
5
  children
@@ -83,4 +83,23 @@ function checkContextVNodePosition(nodes, contextName, componentName) {
83
83
  _srcName === _targetName && console.warn(`\<${contextName}\> can not be a direct child of \<${componentName}\>, it may cause unexpected style behavior, consider lift it up or use it closer to where you want to use it `);
84
84
  }
85
85
  }
86
- export { checkContextVNodePosition, excludeVNodesByName, excludeVNodesByNames, findVNodeByName, findVNodesByName, hasChildVNodeByName, someVNode };
86
+ function isEmptyVNode(node) {
87
+ if (!node) return true;
88
+ const nodes = Array.isArray(node) ? node : [
89
+ node
90
+ ];
91
+ if (0 === nodes.length) return true;
92
+ return nodes.every((n)=>{
93
+ if (!n) return true;
94
+ if (!isVNode(n)) return true;
95
+ if (n.type === Comment) return true;
96
+ if (n.type === Text) return '' === String(n.children ?? '').trim();
97
+ if (n.type === Fragment) {
98
+ const ch = n.children;
99
+ if (null == ch || '' === ch) return true;
100
+ if (Array.isArray(ch)) return ch.every((c)=>isEmptyVNode(c));
101
+ }
102
+ return false;
103
+ });
104
+ }
105
+ export { checkContextVNodePosition, excludeVNodesByName, excludeVNodesByNames, findVNodeByName, findVNodesByName, hasChildVNodeByName, isEmptyVNode, someVNode };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@raxium/vue",
3
3
  "type": "module",
4
- "version": "0.1.2",
4
+ "version": "0.1.3",
5
5
  "description": "Vue core components for Raxium, based on Ark UI",
6
6
  "author": {
7
7
  "name": "Hwacc",
@@ -67,8 +67,8 @@
67
67
  "es-toolkit": "^1.44.0",
68
68
  "lucide-vue-next": "^0.577.0",
69
69
  "vue-component-type-helpers": "^3.2.1",
70
- "@raxium/shared": "0.1.1",
71
- "@raxium/themes": "0.1.2"
70
+ "@raxium/themes": "0.1.3",
71
+ "@raxium/shared": "0.1.1"
72
72
  },
73
73
  "devDependencies": {
74
74
  "@ark-ui/vue": "^5.34.1",
@@ -86,15 +86,16 @@
86
86
  "rsbuild-plugin-unplugin-vue": "^0.2.0",
87
87
  "tailwind-merge": "^3.5.0",
88
88
  "tsc-alias": "^1.8.16",
89
+ "vitest": "^4.1.1",
89
90
  "vue-tsc": "^3.2.4",
90
- "@raxium/shared": "0.1.1",
91
- "@raxium/themes": "0.1.2"
91
+ "@raxium/themes": "0.1.3",
92
+ "@raxium/shared": "0.1.1"
92
93
  },
93
94
  "publishConfig": {
94
95
  "access": "public"
95
96
  },
96
97
  "scripts": {
97
98
  "build": "rslib build --tsconfig ./tsconfig.build.json && vue-tsc --project tsconfig.build.json && tsc-alias --project tsconfig.build.json",
98
- "test": "echo \"Error: no test specified\" && exit 1"
99
+ "test": "vitest run"
99
100
  }
100
101
  }