1+ import { beforeEach , describe , expect , mock , type Mock , spyOn , test } from 'bun:test' ;
2+ import { TestPlugin } from './__mocks__/TestPlugin' ;
3+ import { InputFieldMountable } from 'packages/core/src/fields/inputFields/InputFieldMountable' ;
4+ import { ViewFieldMountable } from 'packages/core/src/fields/viewFields/ViewFieldMountable' ;
5+ import { JsViewFieldMountable } from 'packages/core/src/fields/viewFields/JsViewFieldMountable' ;
6+ import { TableMountable } from 'packages/core/src/fields/metaBindTable/TableMountable' ;
7+ import { ButtonGroupMountable } from 'packages/core/src/fields/button/ButtonGroupMountable' ;
8+ import { ButtonMountable } from 'packages/core/src/fields/button/ButtonMountable' ;
9+ import { EmbedMountable } from 'packages/core/src/fields/embed/EmbedMountable' ;
10+ import { ExcludedMountable } from 'packages/core/src/fields/excluded/ExcludedMountable' ;
11+ import { FieldType , RenderChildType } from 'packages/core/src/config/APIConfigs' ;
12+ import { PropPath } from 'packages/core/src/utils/prop/PropPath' ;
13+ import { PropAccess , PropAccessType } from 'packages/core/src/utils/prop/PropAccess' ;
14+ import { TestComponent } from './__mocks__/TestComponent' ;
15+
16+ describe ( 'api' , ( ) => {
17+ let plugin = new TestPlugin ( ) ;
18+
19+ beforeEach ( ( ) => {
20+ plugin = new TestPlugin ( ) ;
21+ } ) ;
22+
23+ describe ( 'createField' , ( ) => {
24+ test ( 'input field' , ( ) => {
25+ let field = plugin . api . createField ( FieldType . INPUT , '' , {
26+ declaration : 'INPUT[toggle:foo]' ,
27+ renderChildType : RenderChildType . BLOCK ,
28+ } , true ) ;
29+
30+ expect ( field ) . toBeInstanceOf ( InputFieldMountable ) ;
31+ } ) ;
32+
33+ test ( 'view field' , ( ) => {
34+ let field = plugin . api . createField ( FieldType . VIEW , '' , {
35+ declaration : 'VIEW[1 + {foo}]' ,
36+ renderChildType : RenderChildType . BLOCK ,
37+ } , true ) ;
38+
39+ expect ( field ) . toBeInstanceOf ( ViewFieldMountable ) ;
40+ } ) ;
41+
42+ test ( 'js view field' , ( ) => {
43+ let field = plugin . api . createField ( FieldType . JS_VIEW , '' , {
44+ declaration : '{foo} as foo\n---\nreturn 1 + context.bound.foo' ,
45+ } , true ) ;
46+
47+ expect ( field ) . toBeInstanceOf ( JsViewFieldMountable ) ;
48+ } ) ;
49+
50+ test ( 'table field' , ( ) => {
51+ let field = plugin . api . createField ( FieldType . TABLE , '' , {
52+ bindTarget : plugin . api . parseBindTarget ( 'foo' , '' ) ,
53+ columns : [ ] ,
54+ tableHead : [ ] ,
55+ } , true ) ;
56+
57+ expect ( field ) . toBeInstanceOf ( TableMountable ) ;
58+ } ) ;
59+
60+ test ( 'button group field' , ( ) => {
61+ let field = plugin . api . createField ( FieldType . BUTTON_GROUP , '' , {
62+ declaration : 'BUTTON[foo]' ,
63+ renderChildType : RenderChildType . BLOCK ,
64+ } , true ) ;
65+
66+ expect ( field ) . toBeInstanceOf ( ButtonGroupMountable ) ;
67+ } ) ;
68+
69+ test ( 'button field' , ( ) => {
70+ let field = plugin . api . createField ( FieldType . BUTTON , '' , {
71+ declaration : `style: primary
72+ label: Open Meta Bind Playground
73+ class: green-button
74+ action:
75+ type: command
76+ command: obsidian-meta-bind-plugin:open-playground` ,
77+ isPreview : false ,
78+ } , true ) ;
79+
80+ expect ( field ) . toBeInstanceOf ( ButtonMountable ) ;
81+ } ) ;
82+
83+ test ( 'embed field' , ( ) => {
84+ let field = plugin . api . createField ( FieldType . EMBED , '' , {
85+ content : '[[some note]]' ,
86+ depth : 1 ,
87+ } , true ) ;
88+
89+ expect ( field ) . toBeInstanceOf ( EmbedMountable ) ;
90+ } ) ;
91+
92+ test ( 'excluded field' , ( ) => {
93+ let field = plugin . api . createField ( FieldType . EXCLUDED , '' , undefined , true ) ;
94+
95+ expect ( field ) . toBeInstanceOf ( ExcludedMountable ) ;
96+ } ) ;
97+ } ) ;
98+
99+ describe ( 'createInlineFieldFromString' , ( ) => {
100+ test ( 'input field' , ( ) => {
101+ let field = plugin . api . createInlineFieldFromString ( 'INPUT[toggle:foo]' , '' , undefined ) ;
102+
103+ expect ( field ) . toBeInstanceOf ( InputFieldMountable ) ;
104+ } ) ;
105+
106+ test ( 'view field' , ( ) => {
107+ let field = plugin . api . createInlineFieldFromString ( 'VIEW[1 + {foo}]' , '' , undefined ) ;
108+
109+ expect ( field ) . toBeInstanceOf ( ViewFieldMountable ) ;
110+ } ) ;
111+
112+ test ( 'button group field' , ( ) => {
113+ let field = plugin . api . createInlineFieldFromString ( 'BUTTON[foo]' , '' , undefined ) ;
114+
115+ expect ( field ) . toBeInstanceOf ( ButtonGroupMountable ) ;
116+ } ) ;
117+ } ) ;
118+
119+ describe ( 'isInlineFieldDeclaration' , ( ) => {
120+ test ( 'input field' , ( ) => {
121+ expect ( plugin . api . isInlineFieldDeclaration ( FieldType . INPUT , 'INPUT[toggle:foo]' ) ) . toBe ( true ) ;
122+ } ) ;
123+
124+ test ( 'view field' , ( ) => {
125+ expect ( plugin . api . isInlineFieldDeclaration ( FieldType . VIEW , 'VIEW[1 + {foo}]' ) ) . toBe ( true ) ;
126+ } ) ;
127+
128+ test ( 'button group field' , ( ) => {
129+ expect ( plugin . api . isInlineFieldDeclaration ( FieldType . BUTTON_GROUP , 'BUTTON[foo]' ) ) . toBe ( true ) ;
130+ } ) ;
131+
132+ test ( 'not a field' , ( ) => {
133+ expect ( plugin . api . isInlineFieldDeclaration ( FieldType . INPUT , 'foo' ) ) . toBe ( false ) ;
134+ } ) ;
135+
136+ test ( 'wrong field' , ( ) => {
137+ expect ( plugin . api . isInlineFieldDeclaration ( FieldType . INPUT , 'VIEW[1 + {foo}]' ) ) . toBe ( false ) ;
138+ } ) ;
139+ } ) ;
140+
141+ describe ( 'createBindTarget' , ( ) => {
142+ test ( 'simple bind target' , ( ) => {
143+ let bindTarget = plugin . api . createBindTarget ( 'frontmatter' , 'file' , [ 'foo' ] ) ;
144+
145+ expect ( bindTarget ) . toEqual ( {
146+ storageType : 'frontmatter' ,
147+ storagePath : 'file' ,
148+ storageProp : new PropPath ( [ new PropAccess ( PropAccessType . OBJECT , 'foo' ) ] ) ,
149+ listenToChildren : false ,
150+ } ) ;
151+ } ) ;
152+
153+ test ( 'nested bind target' , ( ) => {
154+ let bindTarget = plugin . api . createBindTarget ( 'frontmatter' , 'file' , [ 'foo' , '0' , 'bar' ] ) ;
155+
156+ expect ( bindTarget ) . toEqual ( {
157+ storageType : 'frontmatter' ,
158+ storagePath : 'file' ,
159+ storageProp : new PropPath ( [
160+ new PropAccess ( PropAccessType . OBJECT , 'foo' ) ,
161+ new PropAccess ( PropAccessType . ARRAY , '0' ) ,
162+ new PropAccess ( PropAccessType . OBJECT , 'bar' ) ,
163+ ] ) ,
164+ listenToChildren : false ,
165+ } ) ;
166+ } ) ;
167+ } ) ;
168+
169+ describe ( 'metadata update methods' , ( ) => {
170+ let bindTargetA = plugin . api . parseBindTarget ( 'file#foo' , '' ) ;
171+ let bindTargetB = plugin . api . parseBindTarget ( 'file#bar' , '' ) ;
172+
173+ test . each ( [ 'string' , 5 , false , [ 'array' ] ] ) ( 'setting a value, then reading it reads the same value' , ( value ) => {
174+ plugin . api . setMetadata ( bindTargetA , value ) ;
175+
176+ expect ( plugin . api . getMetadata ( bindTargetA ) ) . toEqual ( value ) ;
177+ } ) ;
178+
179+ test ( 'setting metadata on different bind target does not affect the first one' , ( ) => {
180+ plugin . api . setMetadata ( bindTargetA , 'foo' ) ;
181+ plugin . api . setMetadata ( bindTargetB , 'bar' ) ;
182+
183+ expect ( plugin . api . getMetadata ( bindTargetA ) ) . toEqual ( 'foo' ) ;
184+ } ) ;
185+
186+ test ( 'update callback works correctly' , ( ) => {
187+ plugin . api . setMetadata ( bindTargetA , 0 ) ;
188+
189+ expect ( plugin . api . getMetadata ( bindTargetA ) ) . toEqual ( 0 ) ;
190+
191+ plugin . api . updateMetadata ( bindTargetA , ( value ) => value as number + 1 ) ;
192+
193+ expect ( plugin . api . getMetadata ( bindTargetA ) ) . toEqual ( 1 ) ;
194+ } ) ;
195+
196+ test ( 'subscribing to metadata changes works correctly' , ( ) => {
197+ plugin . api . setMetadata ( bindTargetA , 0 ) ;
198+
199+ let lifecycle = new TestComponent ( ) ;
200+ lifecycle . load ( ) ;
201+ let callback = mock ( ( ) => { } ) ;
202+ plugin . api . subscribeToMetadata ( bindTargetA , lifecycle , callback ) ;
203+
204+ plugin . api . setMetadata ( bindTargetA , 1 ) ;
205+
206+ // two times because the callback is called once when subscribing
207+ expect ( callback ) . toHaveBeenCalledTimes ( 2 ) ;
208+ expect ( callback ) . toHaveBeenLastCalledWith ( 1 ) ;
209+ } ) ;
210+
211+ test ( 'unsubscribing from metadata works correctly' , ( ) => {
212+ plugin . api . setMetadata ( bindTargetA , 0 ) ;
213+
214+ let lifecycle = new TestComponent ( ) ;
215+ lifecycle . load ( ) ;
216+ let callback = mock ( ( ) => { } ) ;
217+ plugin . api . subscribeToMetadata ( bindTargetA , lifecycle , callback ) ;
218+
219+ plugin . api . setMetadata ( bindTargetA , 1 ) ;
220+
221+ lifecycle . unload ( ) ;
222+
223+ plugin . api . setMetadata ( bindTargetA , 2 ) ;
224+
225+ // two times because the callback is called once when subscribing
226+ expect ( callback ) . toHaveBeenCalledTimes ( 2 ) ;
227+ expect ( callback ) . toHaveBeenLastCalledWith ( 1 ) ;
228+ } ) ;
229+ } ) ;
230+ } ) ;
0 commit comments