11import { pull } from "./pull" ;
22import httpClient from "../http/client" ;
3- import { TextItem } from "../http/textItems " ;
3+ import { Component , TextItem } from "../http/types " ;
44import appContext from "../utils/appContext" ;
55import * as path from "path" ;
66import * as fs from "fs" ;
@@ -24,6 +24,19 @@ const createMockTextItem = (overrides: Partial<TextItem> = {}) => ({
2424 ...overrides ,
2525} ) ;
2626
27+ const createMockComponent = ( overrides : Partial < Component > = { } ) => ( {
28+ id : "component-1" ,
29+ text : "Plain text content" ,
30+ richText : "<p>Rich <strong>HTML</strong> content</p>" ,
31+ status : "active" ,
32+ notes : "" ,
33+ tags : [ ] ,
34+ variableIds : [ ] ,
35+ folderId : null ,
36+ variantId : null ,
37+ ...overrides ,
38+ } )
39+
2740const createMockVariable = ( overrides : any = { } ) => ( {
2841 id : "var-1" ,
2942 name : "Variable 1" ,
@@ -36,14 +49,17 @@ const createMockVariable = (overrides: any = {}) => ({
3649} ) ;
3750
3851// Helper functions
39- const setupMocks = ( textItems : TextItem [ ] = [ ] , variables : any [ ] = [ ] ) => {
52+ const setupMocks = ( { textItems = [ ] , components = [ ] , variables = [ ] } : { textItems : TextItem [ ] ; components ?: Component [ ] ; variables ?: any [ ] } ) => {
4053 mockHttpClient . get . mockImplementation ( ( url : string ) => {
4154 if ( url . includes ( "/v2/textItems" ) ) {
4255 return Promise . resolve ( { data : textItems } ) ;
4356 }
4457 if ( url . includes ( "/v2/variables" ) ) {
4558 return Promise . resolve ( { data : variables } ) ;
4659 }
60+ if ( url . includes ( "/v2/components" ) ) {
61+ return Promise . resolve ( { data : components } ) ;
62+ }
4763 return Promise . resolve ( { data : [ ] } ) ;
4864 } ) ;
4965} ;
@@ -55,11 +71,11 @@ const parseJsonFile = (filepath: string) => {
5571
5672const assertFileContainsText = (
5773 filepath : string ,
58- textId : string ,
74+ devId : string ,
5975 expectedText : string
6076) => {
6177 const content = parseJsonFile ( filepath ) ;
62- expect ( content [ textId ] ) . toBe ( expectedText ) ;
78+ expect ( content [ devId ] ) . toBe ( expectedText ) ;
6379} ;
6480
6581const assertFilesCreated = ( outputDir : string , expectedFiles : string [ ] ) => {
@@ -105,11 +121,13 @@ describe("pull command - end-to-end tests", () => {
105121 fs . mkdirSync ( outputDir , { recursive : true } ) ;
106122
107123 const mockTextItem = createMockTextItem ( ) ;
108- setupMocks ( [ mockTextItem ] , [ ] ) ;
124+ const mockComponent = createMockComponent ( ) ;
125+ setupMocks ( { textItems : [ mockTextItem ] , components : [ mockComponent ] } ) ;
109126
110127 // Set up appContext - this is what actually drives the test
111128 appContext . setProjectConfig ( {
112129 projects : [ { id : "project-1" } ] ,
130+ components : { } ,
113131 richText : "html" ,
114132 outputs : [ { format : "json" , outDir : outputDir } ] ,
115133 } ) ;
@@ -122,17 +140,25 @@ describe("pull command - end-to-end tests", () => {
122140 "text-1" ,
123141 "<p>Rich <strong>HTML</strong> content</p>"
124142 ) ;
143+
144+ assertFileContainsText (
145+ path . join ( outputDir , "components___base.json" ) ,
146+ "component-1" ,
147+ "<p>Rich <strong>HTML</strong> content</p>"
148+ )
125149 } ) ;
126150
127151 it ( "should use plain text when richText is disabled at output level" , async ( ) => {
128152 fs . mkdirSync ( outputDir , { recursive : true } ) ;
129153
130154 const mockTextItem = createMockTextItem ( ) ;
131- setupMocks ( [ mockTextItem ] , [ ] ) ;
155+ const mockComponent = createMockComponent ( ) ;
156+ setupMocks ( { textItems : [ mockTextItem ] , components : [ mockComponent ] } ) ;
132157
133158 appContext . setProjectConfig ( {
134159 projects : [ { id : "project-1" } ] ,
135160 richText : "html" ,
161+ components : { } ,
136162 outputs : [ { format : "json" , outDir : outputDir , richText : false } ] ,
137163 } ) ;
138164
@@ -144,13 +170,19 @@ describe("pull command - end-to-end tests", () => {
144170 "text-1" ,
145171 "Plain text content"
146172 ) ;
173+
174+ assertFileContainsText (
175+ path . join ( outputDir , "components___base.json" ) ,
176+ "component-1" ,
177+ "Plain text content"
178+ ) ;
147179 } ) ;
148180
149181 it ( "should use rich text when enabled only at output level" , async ( ) => {
150182 fs . mkdirSync ( outputDir , { recursive : true } ) ;
151183
152184 const mockTextItem = createMockTextItem ( ) ;
153- setupMocks ( [ mockTextItem ] , [ ] ) ;
185+ setupMocks ( { textItems : [ mockTextItem ] } ) ;
154186
155187 appContext . setProjectConfig ( {
156188 projects : [ { id : "project-1" } ] ,
@@ -217,6 +249,137 @@ describe("pull command - end-to-end tests", () => {
217249 } ) ;
218250 } ) ;
219251
252+ it ( "should query components when source field is provided" , async ( ) => {
253+ fs . mkdirSync ( outputDir , { recursive : true } ) ;
254+
255+ appContext . setProjectConfig ( {
256+ components : { } ,
257+ outputs : [
258+ {
259+ format : "json" ,
260+ outDir : outputDir ,
261+ } ,
262+ ] ,
263+ } ) ;
264+
265+ await pull ( ) ;
266+
267+ expect ( mockHttpClient . get ) . toHaveBeenCalledWith ( "/v2/components" , {
268+ params : {
269+ filter :
270+ '{}' ,
271+ } ,
272+ } ) ;
273+ } )
274+
275+ it ( "should filter components by folder at base level" , async ( ) => {
276+ fs . mkdirSync ( outputDir , { recursive : true } ) ;
277+
278+ appContext . setProjectConfig ( {
279+ components : {
280+ folders : [ { id : "folder-1" } ] ,
281+ } ,
282+ outputs : [
283+ {
284+ format : "json" ,
285+ outDir : outputDir ,
286+ } ,
287+ ] ,
288+ } ) ;
289+
290+ await pull ( ) ;
291+
292+ expect ( mockHttpClient . get ) . toHaveBeenCalledWith ( "/v2/components" , {
293+ params : {
294+ filter :
295+ '{"folders":[{"id":"folder-1"}]}' ,
296+ } ,
297+ } ) ;
298+ } )
299+
300+ it ( "should filter components by folder and variants at base level" , async ( ) => {
301+ fs . mkdirSync ( outputDir , { recursive : true } ) ;
302+
303+ appContext . setProjectConfig ( {
304+ components : {
305+ folders : [ { id : "folder-1" } ] ,
306+ } ,
307+ variants : [ { id : "variant-a" } , { id : "variant-b" } ] ,
308+ outputs : [
309+ {
310+ format : "json" ,
311+ outDir : outputDir ,
312+ } ,
313+ ] ,
314+ } ) ;
315+
316+ await pull ( ) ;
317+
318+ expect ( mockHttpClient . get ) . toHaveBeenCalledWith ( "/v2/components" , {
319+ params : {
320+ filter :
321+ '{"folders":[{"id":"folder-1"}],"variants":[{"id":"variant-a"},{"id":"variant-b"}]}' ,
322+ } ,
323+ } ) ;
324+ } )
325+
326+ it ( "should filter components by folder at output level" , async ( ) => {
327+ fs . mkdirSync ( outputDir , { recursive : true } ) ;
328+
329+ appContext . setProjectConfig ( {
330+ components : {
331+ folders : [ { id : "folder-1" } ] ,
332+ } ,
333+ outputs : [
334+ {
335+ format : "json" ,
336+ outDir : outputDir ,
337+ components : {
338+ folders : [ { id : "folder-3" } ]
339+ }
340+ } ,
341+ ] ,
342+ } ) ;
343+
344+ await pull ( ) ;
345+
346+ expect ( mockHttpClient . get ) . toHaveBeenCalledWith ( "/v2/components" , {
347+ params : {
348+ filter :
349+ '{"folders":[{"id":"folder-3"}]}' ,
350+ } ,
351+ } ) ;
352+ } )
353+
354+ it ( "should filter components by folder and variants at output level" , async ( ) => {
355+ fs . mkdirSync ( outputDir , { recursive : true } ) ;
356+
357+ appContext . setProjectConfig ( {
358+ components : {
359+ folders : [ { id : "folder-1" } ] ,
360+ } ,
361+ outputs : [
362+ {
363+ format : "json" ,
364+ outDir : outputDir ,
365+ components : {
366+ folders : [ { id : "folder-3" } ]
367+ } ,
368+ variants : [ { id : "variant-a" } , { id : "variant-b" } ] ,
369+ } ,
370+ ] ,
371+ } ) ;
372+
373+ await pull ( ) ;
374+
375+ expect ( mockHttpClient . get ) . toHaveBeenCalledWith ( "/v2/components" , {
376+ params : {
377+ filter :
378+ '{"folders":[{"id":"folder-3"}],"variants":[{"id":"variant-a"},{"id":"variant-b"}]}' ,
379+ } ,
380+ } ) ;
381+ } )
382+
220383 it ( "should filter projects at output level" , async ( ) => {
221384 fs . mkdirSync ( outputDir , { recursive : true } ) ;
222385
@@ -271,6 +434,7 @@ describe("pull command - end-to-end tests", () => {
271434 fs . mkdirSync ( outputDir , { recursive : true } ) ;
272435
273436 appContext . setProjectConfig ( {
437+ projects : [ ] ,
274438 outputs : [
275439 {
276440 format : "json" ,
@@ -284,16 +448,31 @@ describe("pull command - end-to-end tests", () => {
284448 // Verify correct API call with filtered params
285449 expect ( mockHttpClient . get ) . toHaveBeenCalledWith ( "/v2/textItems" , {
286450 params : {
287- filter : "{}" ,
451+ filter : "{\"projects\":[] }" ,
288452 } ,
289453 } ) ;
454+ expect ( mockHttpClient . get ) . toHaveBeenCalledWith ( "/v2/variables" )
455+
456+ // Components endpoint should not be called if not provided as source field
457+ expect ( mockHttpClient . get ) . toHaveBeenCalledTimes ( 2 )
290458 } ) ;
291459 } ) ;
292460
293461 describe ( "Output files" , ( ) => {
294462 it ( "should create output files for each project and variant returned from the API" , async ( ) => {
295463 fs . mkdirSync ( outputDir , { recursive : true } ) ;
296464
465+ appContext . setProjectConfig ( {
466+ projects : [ ] ,
467+ components : { } ,
468+ outputs : [
469+ {
470+ format : "json" ,
471+ outDir : outputDir ,
472+ } ,
473+ ] ,
474+ } ) ;
475+
297476 // project-1 and project-2 each have at least one base text item
298477 const baseTextItems = [
299478 createMockTextItem ( {
@@ -341,10 +520,54 @@ describe("pull command - end-to-end tests", () => {
341520 } ) ,
342521 ] ;
343522
344- setupMocks (
345- [ ...baseTextItems , ...variantATextItems , ...variantBTextItems ] ,
346- [ ]
347- ) ;
523+ const componentsBase = [
524+ createMockComponent ( {
525+ id : "comp-1" ,
526+ variantId : null ,
527+ folderId : null ,
528+ } ) ,
529+ createMockComponent ( {
530+ id : "comp-2" ,
531+ variantId : null ,
532+ folderId : "folder-1" ,
533+ } ) ,
534+ createMockComponent ( {
535+ id : "comp-3" ,
536+ variantId : null ,
537+ folderId : "folder-2" ,
538+ } ) ,
539+ ]
540+
541+ const componentsVariantA = [
542+ createMockComponent ( {
543+ id : "comp-4" ,
544+ variantId : "variant-a" ,
545+ folderId : null ,
546+ } ) ,
547+ createMockComponent ( {
548+ id : "comp-5" ,
549+ variantId : "variant-a" ,
550+ folderId : "folder-1" ,
551+ } ) ,
552+ ]
553+
554+ const componentsVariantB = [
555+ createMockComponent ( {
556+ id : "comp-6" ,
557+ variantId : "variant-b" ,
558+ folderId : null ,
559+ } ) ,
560+ createMockComponent ( {
561+ id : "comp-7" ,
562+ variantId : "variant-b" ,
563+ folderId : "folder-1" ,
564+ } ) ,
565+ ]
566+
567+ setupMocks ( {
568+ textItems : [ ...baseTextItems , ...variantATextItems , ...variantBTextItems ] ,
569+ components : [ ...componentsBase , ...componentsVariantA , ...componentsVariantB ] ,
570+ } ) ;
348571
349572 await pull ( ) ;
350573
@@ -355,6 +578,9 @@ describe("pull command - end-to-end tests", () => {
355578 "project-1___variant-b.json" ,
356579 "project-2___base.json" ,
357580 "project-2___variant-a.json" ,
581+ "components___base.json" ,
582+ "components___variant-a.json" ,
583+ "components___variant-b.json" ,
358584 "variables.json" ,
359585 ] ) ;
360586 } ) ;
0 commit comments