@oinone/kunlun-vue-widget 6.4.9 → 7.2.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.
Files changed (72) hide show
  1. package/dist/oinone-kunlun-vue-widget.esm.js +1 -16
  2. package/dist/types/index.d.ts +1 -1
  3. package/dist/types/src/basic/AsyncVueWidget.d.ts +7 -7
  4. package/dist/types/src/basic/VueFragment.vue.d.ts +2 -2
  5. package/dist/types/src/basic/VueWidget.d.ts +339 -331
  6. package/dist/types/src/basic/Widget.d.ts +260 -257
  7. package/dist/types/src/basic/index.d.ts +3 -3
  8. package/dist/types/src/data/ActiveRecordsWidget.d.ts +271 -261
  9. package/dist/types/src/data/PathWidget.d.ts +34 -34
  10. package/dist/types/src/data/index.d.ts +2 -2
  11. package/dist/types/src/dsl/DslDefinitionWidget.d.ts +66 -68
  12. package/dist/types/src/dsl/DslNodeWidget.d.ts +42 -42
  13. package/dist/types/src/dsl/DslRenderWidget.d.ts +47 -44
  14. package/dist/types/src/dsl/index.d.ts +3 -3
  15. package/dist/types/src/feature/index.d.ts +1 -1
  16. package/dist/types/src/feature/invisible-supported.d.ts +11 -11
  17. package/dist/types/src/hooks/all-mounted.d.ts +20 -20
  18. package/dist/types/src/hooks/index.d.ts +1 -1
  19. package/dist/types/src/index.d.ts +10 -10
  20. package/dist/types/src/state/context.d.ts +41 -41
  21. package/dist/types/src/state/global.d.ts +3 -4
  22. package/dist/types/src/state/index.d.ts +3 -3
  23. package/dist/types/src/state/method/action.d.ts +18 -18
  24. package/dist/types/src/state/method/field.d.ts +3 -3
  25. package/dist/types/src/state/method/index.d.ts +2 -2
  26. package/dist/types/src/state/typing.d.ts +112 -105
  27. package/dist/types/src/state/use-state.d.ts +15 -16
  28. package/dist/types/src/state/view.d.ts +5 -5
  29. package/dist/types/src/token/index.d.ts +2 -2
  30. package/dist/types/src/typing/WidgetTagContext.d.ts +39 -39
  31. package/dist/types/src/typing/WidgetTagProps.d.ts +23 -23
  32. package/dist/types/src/typing/index.d.ts +3 -3
  33. package/dist/types/src/typing/typing.d.ts +7 -7
  34. package/dist/types/src/util/dsl-render.d.ts +106 -106
  35. package/dist/types/src/util/index.d.ts +4 -4
  36. package/dist/types/src/util/install.d.ts +4 -4
  37. package/dist/types/src/util/render.d.ts +7 -7
  38. package/dist/types/src/util/widget-manager.d.ts +4 -4
  39. package/dist/types/src/view/index.d.ts +161 -161
  40. package/package.json +29 -18
  41. package/src/basic/AsyncVueWidget.ts +2 -2
  42. package/src/basic/VueWidget.ts +67 -31
  43. package/src/basic/Widget.ts +11 -9
  44. package/src/basic/__tests__/Widget.spec.ts +82 -0
  45. package/src/data/ActiveRecordsWidget.ts +51 -21
  46. package/src/data/PathWidget.ts +1 -1
  47. package/src/dsl/DslDefinitionWidget.ts +10 -33
  48. package/src/dsl/DslNodeWidget.ts +3 -3
  49. package/src/dsl/DslRenderWidget.ts +32 -3
  50. package/src/feature/__tests__/invisible-supported.spec.ts +31 -0
  51. package/src/hooks/__tests__/all-mounted.spec.ts +41 -0
  52. package/src/hooks/all-mounted.ts +1 -1
  53. package/src/shim-translate.d.ts +5 -2
  54. package/src/state/__tests__/state.spec.ts +114 -0
  55. package/src/state/context.ts +6 -2
  56. package/src/state/global.ts +16 -5
  57. package/src/state/method/action.ts +7 -2
  58. package/src/state/method/field.ts +1 -1
  59. package/src/state/typing.ts +10 -0
  60. package/src/state/use-state.ts +2 -2
  61. package/src/state/view.ts +3 -3
  62. package/src/token/index.ts +1 -1
  63. package/src/typing/WidgetTagContext.ts +3 -3
  64. package/src/typing/WidgetTagProps.ts +2 -2
  65. package/src/util/__tests__/dsl-render.spec.ts +112 -0
  66. package/src/util/__tests__/render.spec.ts +69 -0
  67. package/src/util/__tests__/widget-manager.spec.ts +27 -0
  68. package/src/util/dsl-render.ts +6 -7
  69. package/src/util/install.ts +1 -1
  70. package/src/util/render.ts +3 -7
  71. package/src/view/index.ts +15 -8
  72. package/rollup.config.js +0 -22
@@ -1,4 +1,4 @@
1
- import { IWidget, WidgetConstructor, WidgetProps } from '@oinone/kunlun-engine';
1
+ import type { IWidget, WidgetConstructor, WidgetProps } from '@oinone/kunlun-engine';
2
2
  import { instantiate } from '@oinone/kunlun-shared';
3
3
  import { BehaviorSubject, Subject, Subscription } from '@oinone/kunlun-state';
4
4
  import { InnerWidgetType } from '../typing/typing';
@@ -32,6 +32,8 @@ export interface WidgetBehaviorSubjection<T> extends BaseWidgetSubjection<T> {
32
32
  export type watcher<T> = { path: string; handler: (newVal: T, oldVal: T) => void; options?: { deep?: boolean } };
33
33
  export type HANDLE = string;
34
34
 
35
+ type InjectionType = { name: typeof Widget; list: Map<string | Symbol, string> };
36
+
35
37
  export abstract class Widget<Props extends WidgetProps = WidgetProps, R = unknown> implements IWidget<Props> {
36
38
  private static widgetCount = 0;
37
39
 
@@ -223,10 +225,10 @@ export abstract class Widget<Props extends WidgetProps = WidgetProps, R = unknow
223
225
  */
224
226
  protected static Inject(injectName?: string | Symbol) {
225
227
  return <T extends Widget>(target: T, name: string) => {
226
- let injection: { name: typeof Widget; list: Map<string | Symbol, string> } = Reflect.get(target, 'injection') || {
228
+ let injection = (Reflect.get(target, 'injection') || {
227
229
  name: this,
228
230
  list: new Map()
229
- };
231
+ }) as InjectionType;
230
232
  if (injection.name !== target.constructor) {
231
233
  const oldInjection = injection;
232
234
  injection = { name: target.constructor as typeof Widget, list: new Map() };
@@ -260,10 +262,10 @@ export abstract class Widget<Props extends WidgetProps = WidgetProps, R = unknow
260
262
  */
261
263
  protected static Provide(provideName?: string | Symbol) {
262
264
  return <T extends Widget>(target: T, name: string) => {
263
- let injection: { name: typeof Widget; list: Map<string | Symbol, string> } = Reflect.get(target, 'provider') || {
265
+ let injection = (Reflect.get(target, 'provider') || {
264
266
  name: this,
265
267
  list: new Map()
266
- };
268
+ }) as InjectionType;
267
269
  if (injection.name !== (target.constructor as typeof Widget)) {
268
270
  const old = injection;
269
271
  injection = { name: target.constructor as typeof Widget, list: new Map() };
@@ -319,7 +321,7 @@ export abstract class Widget<Props extends WidgetProps = WidgetProps, R = unknow
319
321
  }
320
322
 
321
323
  const props = {};
322
- const injection = (Reflect.get(this, 'injection') || { list: new Map() }).list as Map<string | Symbol, string>;
324
+ const injection = ((Reflect.get(this, 'injection') || { list: new Map() }) as InjectionType).list;
323
325
  const attrs = this.getAttributes();
324
326
 
325
327
  if (injection) {
@@ -378,7 +380,7 @@ export abstract class Widget<Props extends WidgetProps = WidgetProps, R = unknow
378
380
  * 包装发布者事件
379
381
  * 如果启动了作用域检查,那么发布的时候会携带发布者的widget实例
380
382
  **/
381
- private wrapSubjectWithScope(subject: AnySubject, scope?: WidgetTypeKey): AnySubject {
383
+ private wrapSubjectWithScope(subject: AnySubject<any>, scope?: WidgetTypeKey): AnySubject {
382
384
  if (!scope) {
383
385
  return subject;
384
386
  }
@@ -763,7 +765,7 @@ export abstract class Widget<Props extends WidgetProps = WidgetProps, R = unknow
763
765
  }
764
766
 
765
767
  private releaseInjection() {
766
- const injection: { list: Map<string | Symbol, string> } = Reflect.get(this, 'injection');
768
+ const injection = Reflect.get(this, 'injection') as InjectionType;
767
769
  if (injection) {
768
770
  injection.list.forEach((name) => {
769
771
  const widget = this.getSelf()!;
@@ -778,7 +780,7 @@ export abstract class Widget<Props extends WidgetProps = WidgetProps, R = unknow
778
780
  }
779
781
 
780
782
  public getComputeHandler(name: string) {
781
- return Reflect.get(this, `$compute$${name}`);
783
+ return Reflect.get(this, `$compute$${name}`) as { get: Function; set: Function };
782
784
  }
783
785
 
784
786
  protected getProps() {
@@ -0,0 +1,82 @@
1
+ import { Widget } from '../Widget';
2
+ import type { WidgetBehaviorSubjection } from '../Widget';
3
+
4
+ class TestWidget extends Widget<{ value: number }, null> {
5
+ @Widget.Reactive({ displayName: 'displayValue' })
6
+ public value = 0;
7
+
8
+ @Widget.Method('increment')
9
+ public increment() {
10
+ this.value += 1;
11
+ return this.value;
12
+ }
13
+
14
+ @Widget.Watch('displayValue')
15
+ public onValueChange() {}
16
+
17
+ public render() {
18
+ return null;
19
+ }
20
+ }
21
+
22
+ const behaviorIdentifier = Symbol('behavior-sub-context');
23
+
24
+ class BehaviorWidget extends Widget<{}, null> {
25
+ @Widget.BehaviorSubContext(behaviorIdentifier)
26
+ public channel!: WidgetBehaviorSubjection<{ value: string }>;
27
+
28
+ public render() {
29
+ return null;
30
+ }
31
+ }
32
+
33
+ describe('Widget 基类', () => {
34
+ it('Reactive/Method 装饰器应注册属性元数据', () => {
35
+ const widget = new TestWidget().initialize({ value: 1 } as any) as TestWidget;
36
+ const attrs = widget.getAttributes();
37
+ const props = (widget as any).getProps();
38
+
39
+ expect(attrs.get('displayValue')).toBe('value');
40
+ expect(props).toContain('displayValue');
41
+
42
+ const handle = widget.getHandle();
43
+ const selected = Widget.select<TestWidget>(handle);
44
+ expect(selected).toBe(widget);
45
+ });
46
+
47
+ it('Watch 装饰器应注册监听配置', () => {
48
+ const widget = new TestWidget().initialize({ value: 1 } as any) as TestWidget;
49
+ const watchers = widget.getWatchers();
50
+
51
+ expect(watchers.length).toBeGreaterThan(0);
52
+ expect(watchers.some((w) => w.path === 'displayValue')).toBe(true);
53
+ });
54
+
55
+ it('dispose 应从全局注册表中移除实例', () => {
56
+ const widget = new TestWidget().initialize({ value: 1 } as any) as TestWidget;
57
+ const handle = widget.getHandle();
58
+
59
+ expect(Widget.select(handle)).toBe(widget);
60
+
61
+ widget.dispose();
62
+
63
+ expect(Widget.select(handle)).toBeUndefined();
64
+ });
65
+
66
+ it('BehaviorSubContext 应支持组件间通信', () => {
67
+ const publisher = new BehaviorWidget().initialize() as BehaviorWidget;
68
+ const subscriber = new BehaviorWidget().initialize() as BehaviorWidget;
69
+
70
+ const received: string[] = [];
71
+
72
+ subscriber.channel.subscribe((data) => {
73
+ if (data) {
74
+ received.push(data.value);
75
+ }
76
+ });
77
+
78
+ publisher.channel.subject.next({ value: 'hello' });
79
+
80
+ expect(received).toContain('hello');
81
+ });
82
+ });
@@ -1,28 +1,30 @@
1
1
  import {
2
- ActiveRecord,
3
- ActiveRecords,
2
+ type ActiveRecord,
3
+ type ActiveRecords,
4
4
  ActiveRecordsOperator,
5
- DeleteActiveRecordsByEntityFunction,
6
- DeleteActiveRecordsByEntityPredict,
7
- DeleteActiveRecordsFunction,
8
- FlushActiveRecordsFunction,
9
- PushActiveRecordsFunction,
10
- PushActiveRecordsPredict,
11
- ReloadActiveRecordsFunction,
12
- RuntimeRelationField,
5
+ type DeleteActiveRecordsByEntityFunction,
6
+ type DeleteActiveRecordsByEntityPredict,
7
+ type DeleteActiveRecordsFunction,
8
+ type FlushActiveRecordsFunction,
9
+ type PushActiveRecordsFunction,
10
+ type PushActiveRecordsPredict,
11
+ type ReloadActiveRecordsFunction,
12
+ type RuntimeRelationField,
13
13
  SubmitCacheManager,
14
- UpdateActiveRecordsByEntityFunction,
15
- UpdateActiveRecordsByEntityPredict,
16
- UpdateActiveRecordsFunction,
17
- UpdateEntity
14
+ type UpdateActiveRecordsByEntityFunction,
15
+ type UpdateActiveRecordsByEntityPredict,
16
+ type UpdateActiveRecordsFunction,
17
+ type UpdateEntity
18
18
  } from '@oinone/kunlun-engine';
19
19
  import { Widget } from '../basic';
20
- import { OioAnyViewState, useOioState } from '../state';
21
- import { PathWidget, PathWidgetProps } from './PathWidget';
20
+ import { type OioAnyViewState, OioGlobalState, useOioState } from '../state';
21
+ import { PathWidget, type PathWidgetProps } from './PathWidget';
22
22
 
23
23
  export interface ActiveRecordsWidgetProps extends PathWidgetProps {
24
24
  dataSource?: ActiveRecords | null;
25
25
  activeRecords?: ActiveRecords;
26
+ isInitViewState?: boolean;
27
+ viewState?: OioAnyViewState;
26
28
  }
27
29
 
28
30
  /**
@@ -31,6 +33,22 @@ export interface ActiveRecordsWidgetProps extends PathWidgetProps {
31
33
  export class ActiveRecordsWidget<
32
34
  Props extends ActiveRecordsWidgetProps = ActiveRecordsWidgetProps
33
35
  > extends PathWidget<Props> {
36
+ /**
37
+ * 全局状态,在 beforeMounted 后可获取到有效值
38
+ * @protected
39
+ */
40
+ protected globalState: OioGlobalState | undefined;
41
+
42
+ public getGlobalState(): OioGlobalState | undefined {
43
+ return this.globalState;
44
+ }
45
+
46
+ public setGlobalState(state: OioGlobalState) {
47
+ this.globalState = state;
48
+ }
49
+
50
+ protected isInitViewState?: boolean;
51
+
34
52
  /**
35
53
  * 视图级别状态, 在 beforeMounted 后可获取到有效值
36
54
  * @protected
@@ -47,9 +65,13 @@ export class ActiveRecordsWidget<
47
65
 
48
66
  public initialize(props: Props) {
49
67
  super.initialize(props);
50
- const { dataSource, activeRecords } = props;
68
+ const { dataSource, activeRecords, viewState } = props;
51
69
  this.setCurrentDataSource(dataSource);
52
70
  this.setCurrentActiveRecords(activeRecords);
71
+ if (viewState) {
72
+ this.isInitViewState = props.isInitViewState ?? true;
73
+ this.setViewState(viewState);
74
+ }
53
75
  return this;
54
76
  }
55
77
 
@@ -587,8 +609,16 @@ export class ActiveRecordsWidget<
587
609
  protected $$beforeMount() {
588
610
  super.$$beforeMount();
589
611
  let isInitStatePosition = false;
590
- if (!this.viewState) {
591
- this.viewState = useOioState().viewState;
612
+ if (this.viewState) {
613
+ if (this.isInitViewState) {
614
+ isInitStatePosition = true;
615
+ this.$$initViewStatePosition(this.viewState);
616
+ this.$$initViewState(this.viewState);
617
+ }
618
+ } else {
619
+ const { globalState, viewState } = useOioState();
620
+ this.globalState = globalState;
621
+ this.viewState = viewState;
592
622
  if (this.viewState) {
593
623
  isInitStatePosition = true;
594
624
  this.$$initViewStatePosition(this.viewState);
@@ -600,8 +630,8 @@ export class ActiveRecordsWidget<
600
630
  }
601
631
  }
602
632
 
603
- protected $$mounted() {
604
- super.$$mounted();
633
+ protected $$mountedAfterProperties() {
634
+ super.$$mountedAfterProperties();
605
635
  if (this.viewState) {
606
636
  this.$$clearViewStatePosition(this.viewState);
607
637
  this.$$clearViewState(this.viewState);
@@ -1,6 +1,6 @@
1
1
  import { isNil } from 'lodash-es';
2
2
  import { Widget } from '../basic';
3
- import { DslDefinitionWidgetProps, DslDefinitionWidget } from '../dsl';
3
+ import { type DslDefinitionWidgetProps, DslDefinitionWidget } from '../dsl';
4
4
 
5
5
  export interface PathWidgetProps extends DslDefinitionWidgetProps {
6
6
  path?: string;
@@ -1,18 +1,17 @@
1
- import { getMergeConfig } from '@oinone/kunlun-config';
2
1
  import {
3
- ComputeContext,
2
+ type ComputeContext,
4
3
  ComputeContextManager,
5
4
  ROOT_HANDLE,
6
- RuntimeContext,
5
+ type RuntimeContext,
7
6
  RuntimeContextManager,
8
- RuntimeModelField
7
+ type RuntimeModelField
9
8
  } from '@oinone/kunlun-engine';
10
- import { BooleanHelper, CSSClass, CSSStyle } from '@oinone/kunlun-shared';
9
+ import { BooleanHelper, type CSSClass, type CSSStyle } from '@oinone/kunlun-shared';
11
10
  import { StyleHelper } from '@oinone/kunlun-vue-ui-common';
12
11
  import { isNil } from 'lodash-es';
13
12
  import { Widget } from '../basic';
14
- import { InvisibleSupported, isAllInvisible } from '../feature';
15
- import { DslRenderWidget, DslRenderWidgetProps } from './DslRenderWidget';
13
+ import { type InvisibleSupported, isAllInvisible } from '../feature';
14
+ import { DslRenderWidget, type DslRenderWidgetProps } from './DslRenderWidget';
16
15
 
17
16
  /**
18
17
  * dsl组件属性
@@ -183,7 +182,7 @@ export class DslDefinitionWidget<Props extends DslDefinitionWidgetProps = DslDef
183
182
 
184
183
  public get rootViewRuntimeContext(): { runtimeContext: RuntimeContext; fields: RuntimeModelField[] } {
185
184
  const fields: RuntimeModelField[] = [];
186
- let targetRuntimeContext: RuntimeContext = this.rootRuntimeContext;
185
+ let targetRuntimeContext: RuntimeContext | undefined = this.rootRuntimeContext;
187
186
  let field = targetRuntimeContext?.parentContext?.field;
188
187
  while (field && targetRuntimeContext) {
189
188
  fields.push(field);
@@ -204,37 +203,15 @@ export class DslDefinitionWidget<Props extends DslDefinitionWidgetProps = DslDef
204
203
  field = parentRuntimeContext.field;
205
204
  targetRuntimeContext = nextTargetRuntimeContext;
206
205
  }
206
+ if (!targetRuntimeContext) {
207
+ throw new Error('Invalid target runtime context.');
208
+ }
207
209
  return {
208
210
  runtimeContext: targetRuntimeContext,
209
211
  fields
210
212
  };
211
213
  }
212
214
 
213
- protected cacheConfigProxy;
214
-
215
- protected getMergeConfig(...keys: string[]): Record<string, any> {
216
- // dsl
217
- // appConfig
218
- // themeConfig
219
- // runtime config ConfigHelper
220
- if (this.cacheConfigProxy) {
221
- return this.cacheConfigProxy;
222
- }
223
- const result = getMergeConfig(keys, {
224
- defaultValue: undefined
225
- });
226
- this.cacheConfigProxy = new Proxy(this.getDsl(), {
227
- get(target, prop) {
228
- if (prop in target) {
229
- return target[prop as keyof typeof target];
230
- }
231
- return result[prop as keyof typeof result];
232
- }
233
- });
234
-
235
- return this.cacheConfigProxy;
236
- }
237
-
238
215
  protected invisibleProcess(invisible: boolean | string) {
239
216
  return BooleanHelper.toBoolean(invisible);
240
217
  }
@@ -1,6 +1,6 @@
1
- import { DslDefinition, XMLTemplateParser } from '@oinone/kunlun-dsl';
2
- import { DslProps, RuntimeContext, RuntimeContextManager } from '@oinone/kunlun-engine';
3
- import { IDslNode } from '@oinone/kunlun-meta';
1
+ import { type DslDefinition, XMLTemplateParser } from '@oinone/kunlun-dsl';
2
+ import { type DslProps, type RuntimeContext, RuntimeContextManager } from '@oinone/kunlun-engine';
3
+ import type { IDslNode } from '@oinone/kunlun-meta';
4
4
  import { isNil } from 'lodash-es';
5
5
  import { VueWidget, Widget } from '../basic';
6
6
 
@@ -1,6 +1,7 @@
1
- import { DslDefinition, DslSlots, DslSlotUtils, UnknownDslDefinition } from '@oinone/kunlun-dsl';
2
- import { WidgetProps } from '@oinone/kunlun-engine';
3
- import { Slots, VNode } from 'vue';
1
+ import { getMergeConfig } from '@oinone/kunlun-config';
2
+ import { type DslDefinition, type DslSlots, DslSlotUtils, UnknownDslDefinition } from '@oinone/kunlun-dsl';
3
+ import type { WidgetProps } from '@oinone/kunlun-engine';
4
+ import type { Slots, VNode } from 'vue';
4
5
  import { VueWidget, Widget } from '../basic';
5
6
  import { DslRender } from '../util';
6
7
 
@@ -40,6 +41,8 @@ export class DslRenderWidget<Props extends DslRenderWidgetProps = DslRenderWidge
40
41
  @Widget.Reactive()
41
42
  protected slotName: string | undefined;
42
43
 
44
+ protected renderProps: Props | undefined;
45
+
43
46
  protected supportedSlotNames!: string[];
44
47
 
45
48
  protected dslSlots: DslSlots | undefined;
@@ -48,6 +51,7 @@ export class DslRenderWidget<Props extends DslRenderWidgetProps = DslRenderWidge
48
51
 
49
52
  public initialize(props: Props) {
50
53
  super.initialize(props);
54
+ this.renderProps = props;
51
55
  this.internal = props.internal || false;
52
56
  this.template = props.template;
53
57
  this.slotName = props.slotName;
@@ -62,6 +66,31 @@ export class DslRenderWidget<Props extends DslRenderWidgetProps = DslRenderWidge
62
66
  return this.template || UnknownDslDefinition;
63
67
  }
64
68
 
69
+ protected cacheConfigProxy;
70
+
71
+ protected getMergeConfig(...keys: string[]): Record<string, any> {
72
+ // dsl
73
+ // appConfig
74
+ // themeConfig
75
+ // runtime config ConfigHelper
76
+ if (this.cacheConfigProxy) {
77
+ return this.cacheConfigProxy;
78
+ }
79
+ const result = getMergeConfig(keys, {
80
+ defaultValue: undefined
81
+ });
82
+ this.cacheConfigProxy = new Proxy(this.getDsl(), {
83
+ get(target, prop) {
84
+ if (prop in target) {
85
+ return target[prop as keyof typeof target];
86
+ }
87
+ return result[prop as keyof typeof result];
88
+ }
89
+ });
90
+
91
+ return this.cacheConfigProxy;
92
+ }
93
+
65
94
  public getSlotName() {
66
95
  return this.slotName;
67
96
  }
@@ -0,0 +1,31 @@
1
+ import { executeInvisible, isAllInvisible, type InvisibleSupported } from '../invisible-supported';
2
+
3
+ describe('invisible-supported', () => {
4
+ it('executeInvisible: 不传或为 undefined 时返回 false', () => {
5
+ expect(executeInvisible(undefined)).toBe(false);
6
+ expect(executeInvisible({ invisible: undefined })).toBe(false);
7
+ });
8
+
9
+ it('executeInvisible: 布尔值时直接返回布尔值', () => {
10
+ expect(executeInvisible({ invisible: true })).toBe(true);
11
+ expect(executeInvisible({ invisible: false })).toBe(false);
12
+ });
13
+
14
+ it('executeInvisible: 函数时执行并返回结果', () => {
15
+ const supported: InvisibleSupported = {
16
+ invisible: () => true
17
+ };
18
+ expect(executeInvisible(supported)).toBe(true);
19
+ });
20
+
21
+ it('isAllInvisible: 空数组或 undefined 返回 true, 否则需全部为 invisible', () => {
22
+ expect(isAllInvisible(undefined)).toBe(true);
23
+ expect(isAllInvisible([])).toBe(true);
24
+ expect(
25
+ isAllInvisible([{ invisible: true } as InvisibleSupported, { invisible: () => true } as InvisibleSupported])
26
+ ).toBe(true);
27
+ expect(
28
+ isAllInvisible([{ invisible: true } as InvisibleSupported, { invisible: false } as InvisibleSupported])
29
+ ).toBe(false);
30
+ });
31
+ });
@@ -0,0 +1,41 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import { defineComponent, h, nextTick } from 'vue';
3
+ import { onAllMounted, reportAllMounted } from '../all-mounted';
4
+
5
+ describe('onAllMounted & reportAllMounted', () => {
6
+ it('不传回调时应打印警告且不抛错', () => {
7
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
8
+ // @ts-expect-error
9
+ onAllMounted(undefined);
10
+ expect(warnSpy).toHaveBeenCalled();
11
+ warnSpy.mockRestore();
12
+ });
13
+
14
+ it('所有子组件挂载后应触发 allMounted 回调', async () => {
15
+ const allMounted = jest.fn();
16
+ const allMountedUpdate = jest.fn();
17
+
18
+ const Parent = defineComponent({
19
+ setup() {
20
+ onAllMounted({ allMounted, allMountedUpdate });
21
+ return () => h('div', [h(Child), h(Child)]);
22
+ }
23
+ });
24
+
25
+ const Child = defineComponent({
26
+ setup() {
27
+ reportAllMounted();
28
+ return () => h('div');
29
+ }
30
+ });
31
+
32
+ const wrapper = mount(Parent);
33
+
34
+ await nextTick();
35
+ expect(allMounted).toHaveBeenCalledTimes(1);
36
+
37
+ wrapper.unmount();
38
+ await nextTick();
39
+ expect(allMountedUpdate).toHaveBeenCalledTimes(0);
40
+ });
41
+ });
@@ -1,6 +1,6 @@
1
1
  import { isFunction, isNil } from 'lodash-es';
2
2
 
3
- import { inject, InjectionKey, onBeforeMount, onMounted, onUnmounted, provide } from 'vue';
3
+ import { inject, type InjectionKey, onBeforeMount, onMounted, onUnmounted, provide } from 'vue';
4
4
 
5
5
  interface AllMountedContext {
6
6
  reportBeforeMount: (key: string) => void;
@@ -1,7 +1,10 @@
1
1
  export {};
2
2
 
3
- declare module '@vue/runtime-core' {
3
+ declare module 'vue' {
4
4
  interface ComponentCustomProperties {
5
- $translate<T extends string | null | undefined = string | null | undefined>(text: T): T;
5
+ $translate<T extends string | null | undefined = string | null | undefined>(
6
+ text: T,
7
+ context?: Record<string, unknown>
8
+ ): T;
6
9
  }
7
10
  }
@@ -0,0 +1,114 @@
1
+ import { computed, createApp, defineComponent, h } from 'vue';
2
+ import { ViewType } from '@oinone/kunlun-meta';
3
+ import { useProviderMetaContext, useInjectMetaContext, defaultMetaContext } from '../context';
4
+ import { clearViewState, createViewState, getViewState, setViewState } from '../view';
5
+ import { globalState } from '../global';
6
+ import { useOioState } from '../use-state';
7
+ import { createActionBarState, getActionBarState, popAction, pushAction } from '../method/action';
8
+ import { popField, pushField } from '../method/field';
9
+
10
+ describe('state/context', () => {
11
+ it('useProviderMetaContext/ useInjectMetaContext 应合并默认值和传入状态', () => {
12
+ let ctx: ReturnType<typeof useInjectMetaContext> | undefined;
13
+
14
+ const App = defineComponent({
15
+ setup() {
16
+ useProviderMetaContext({});
17
+ return () =>
18
+ h(
19
+ defineComponent({
20
+ setup() {
21
+ ctx = useInjectMetaContext();
22
+ return () => null;
23
+ }
24
+ })
25
+ );
26
+ }
27
+ });
28
+
29
+ const app = createApp(App);
30
+ const container = document.createElement('div');
31
+ app.mount(container);
32
+
33
+ expect(ctx).toBeDefined();
34
+ expect(ctx!.viewType.value).toBe(defaultMetaContext.viewType.value);
35
+ expect(ctx!.model.value).toBe(defaultMetaContext.model.value);
36
+
37
+ app.unmount();
38
+ });
39
+ });
40
+
41
+ describe('state/view & use-state', () => {
42
+ it('createViewState/ setViewState/ getViewState/ clearViewState 应维护视图状态', () => {
43
+ const handle = 'view-handle';
44
+ const state = createViewState(handle);
45
+ expect(state.handle).toBe(handle);
46
+
47
+ setViewState(state);
48
+ expect(getViewState(handle)).toBe(state);
49
+
50
+ const cleared = clearViewState(handle);
51
+ expect(cleared).toBe(state);
52
+ expect(getViewState(handle)).toBeUndefined();
53
+ });
54
+
55
+ it('useOioState 不带 handle 时返回全局状态和当前 viewState 方法', () => {
56
+ const handle = 'view-handle-2';
57
+ const state = createViewState(handle);
58
+ setViewState(state);
59
+
60
+ const result = useOioState();
61
+ expect(result.globalState).toBe(globalState);
62
+ expect(result.viewState).toBeUndefined();
63
+ expect(typeof result.createViewState).toBe('function');
64
+ });
65
+
66
+ it('useOioState 带 handle 时返回该视图的快捷操作函数', () => {
67
+ const handle = 'view-handle-3';
68
+ const state = createViewState(handle);
69
+ setViewState(state);
70
+
71
+ const result = useOioState(handle);
72
+ expect(result.globalState).toBe(globalState);
73
+ expect(result.viewState).toBe(state);
74
+
75
+ const newState = result.createViewState();
76
+ expect(newState.handle).toBe(handle);
77
+ expect(result.getViewState()).toBe(newState);
78
+
79
+ const removed = result.clearViewState();
80
+ expect(removed).toBe(newState);
81
+ expect(result.getViewState()).toBeUndefined();
82
+ });
83
+ });
84
+
85
+ describe('state/method/action', () => {
86
+ it('createActionBarState/getActionBarState/pushAction/popAction 应正确维护动作条', () => {
87
+ const viewState = createViewState('action-view');
88
+ const actionBar = createActionBarState.call(viewState, { handle: 'action-view' });
89
+ (viewState as any).actionBar = actionBar;
90
+
91
+ const currentActionBar = getActionBarState.call(viewState);
92
+ expect(currentActionBar).toBe(actionBar);
93
+
94
+ pushAction.call(viewState, 'action1');
95
+ expect(actionBar.actions).toContain('action1');
96
+
97
+ popAction.call(viewState, 'action1');
98
+ expect(actionBar.actions).not.toContain('action1');
99
+ });
100
+ });
101
+
102
+ describe('state/method/field', () => {
103
+ it('pushField/popField 应维护字段集合', () => {
104
+ const viewState = createViewState('field-view');
105
+ (viewState as any).fields = [];
106
+ (viewState as any).viewType = ViewType.Table;
107
+
108
+ pushField.call(viewState, 'field1');
109
+ expect(viewState.fields).toContain('field1');
110
+
111
+ popField.call(viewState, 'field1');
112
+ expect(viewState.fields).not.toContain('field1');
113
+ });
114
+ });
@@ -1,5 +1,5 @@
1
1
  import { ViewType } from '@oinone/kunlun-meta';
2
- import { computed, ComputedRef, inject, InjectionKey, provide } from 'vue';
2
+ import { computed, type ComputedRef, inject, type InjectionKey, provide } from 'vue';
3
3
 
4
4
  /**
5
5
  * 元数据上下文
@@ -53,5 +53,9 @@ export const useProviderMetaContext = (state: Partial<MetaContext>): void => {
53
53
  * 获取元数据上下文
54
54
  */
55
55
  export const useInjectMetaContext = (): MetaContext => {
56
- return inject(MetaContextKey, defaultMetaContext);
56
+ const res = inject(MetaContextKey, defaultMetaContext);
57
+ if (!res) {
58
+ return { ...defaultMetaContext };
59
+ }
60
+ return res;
57
61
  };