Skip to content

Commit 978b517

Browse files
authored
[DIT 11281] CLI support for components (#124)
* Add support for fetching components from API for JSON output * clean up logs, basic test * make folders optional, add tests * split up filter rode, clean up error logging * support nested folder specification * upgrade to beta 8 version
1 parent 53643cb commit 978b517

11 files changed

Lines changed: 523 additions & 94 deletions

File tree

lib/src/commands/pull.test.ts

Lines changed: 238 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { pull } from "./pull";
22
import httpClient from "../http/client";
3-
import { TextItem } from "../http/textItems";
3+
import { Component, TextItem } from "../http/types";
44
import appContext from "../utils/appContext";
55
import * as path from "path";
66
import * 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+
2740
const 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

5672
const 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

6581
const 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

Comments
 (0)