diff --git a/src/resolve/sourceComponent.ts b/src/resolve/sourceComponent.ts index c9781f637..2ec114d8e 100644 --- a/src/resolve/sourceComponent.ts +++ b/src/resolve/sourceComponent.ts @@ -188,8 +188,14 @@ export class SourceComponent implements MetadataComponent { public async parseXml(xmlFilePath?: string): Promise { const xml = xmlFilePath ?? this.xml; if (xml) { - const contents = this.pathContentMap.get(xml) ?? (await this.tree.readFile(xml)).toString(); - this.pathContentMap.set(xml, contents); + let contents: string; + if (this.pathContentMap.has(xml)) { + contents = this.pathContentMap.get(xml) as string; + } else { + contents = (await this.tree.readFile(xml)).toString(); + this.pathContentMap.set(xml, contents); + } + const replacements = this.replacements?.[xml] ?? this.parent?.replacements?.[xml]; return this.parseAndValidateXML( replacements ? await replacementIterations(contents, replacements) : contents, @@ -202,8 +208,14 @@ export class SourceComponent implements MetadataComponent { public parseXmlSync(xmlFilePath?: string): T { const xml = xmlFilePath ?? this.xml; if (xml) { - const contents = this.pathContentMap.get(xml) ?? this.tree.readFileSync(xml).toString(); - this.pathContentMap.set(xml, contents); + let contents: string; + if (this.pathContentMap.has(xml)) { + contents = this.pathContentMap.get(xml) as string; + } else { + contents = this.tree.readFileSync(xml).toString(); + this.pathContentMap.set(xml, contents); + } + return this.parseAndValidateXML(contents, xml); } return {} as T; diff --git a/test/convert/convertContext/nonDecomposition.test.ts b/test/convert/convertContext/nonDecomposition.test.ts index d79b2c98a..f789b81c1 100644 --- a/test/convert/convertContext/nonDecomposition.test.ts +++ b/test/convert/convertContext/nonDecomposition.test.ts @@ -24,7 +24,7 @@ import { } from '../../mock/type-constants/customlabelsConstant'; import { SourceComponent } from '../../../src'; -describe.skip('NonDecomposition', () => { +describe('NonDecomposition', () => { const env = createSandbox(); afterEach(() => env.restore()); @@ -41,7 +41,7 @@ describe.skip('NonDecomposition', () => { } as unknown as SfProject); }); it('should return WriterFormats for claimed children', async () => { - const component = new SourceComponent(nonDecomposed.COMPONENT_1, TREE); + const component = nonDecomposed.COMPONENT_1; const context = new ConvertContext(); const writeInfos = [ { @@ -58,6 +58,8 @@ describe.skip('NonDecomposition', () => { }; const result = await context.nonDecomposition.finalize(nonDecomposed.DEFAULT_DIR, TREE); + // @ts-ignore - because the resulting component will have cache info, and the initial one won't + result[0].component.pathContentMap = new Map(); expect(result).to.deep.equal([{ component, writeInfos }]); }); @@ -95,7 +97,7 @@ describe.skip('NonDecomposition', () => { }); it('should merge unclaimed children to default parent component', async () => { - const component = new SourceComponent(nonDecomposed.COMPONENT_1); + const component = nonDecomposed.COMPONENT_1; const type = component.type; const context = new ConvertContext(); @@ -116,7 +118,8 @@ describe.skip('NonDecomposition', () => { }; const result = await context.nonDecomposition.finalize(nonDecomposed.DEFAULT_DIR, TREE); - + // @ts-ignore - because the resulting component will have cache info, and the initial one won't + result[0].component.pathContentMap = new Map(); expect(result).to.deep.equal([{ component, writeInfos }]); }); @@ -145,6 +148,8 @@ describe.skip('NonDecomposition', () => { }; const result = await context.nonDecomposition.finalize(nonDecomposed.DEFAULT_DIR, TREE); + // @ts-ignore - because the resulting component will have cache info, and the initial one won't + result[0].component.pathContentMap = new Map(); expect(result).to.deep.equal([{ component, writeInfos }]); }); @@ -164,7 +169,7 @@ describe.skip('NonDecomposition', () => { }, ], } as unknown as SfProject); - const component = new SourceComponent(nonDecomposed.COMPONENT_2); + const component = nonDecomposed.COMPONENT_2; const context = new ConvertContext(); const type = component.type; @@ -188,6 +193,8 @@ describe.skip('NonDecomposition', () => { }; const result = await context.nonDecomposition.finalize(nonDecomposed.DEFAULT_DIR, TREE); + // @ts-ignore - because the resulting component will have cache info, and the initial one won't + result[0].component.pathContentMap = new Map(); expect(result).to.deep.equal([{ component, writeInfos }]); }); }); diff --git a/test/convert/convertContext/recomposition.test.ts b/test/convert/convertContext/recomposition.test.ts index b57e3b3ed..c05c0cb2e 100644 --- a/test/convert/convertContext/recomposition.test.ts +++ b/test/convert/convertContext/recomposition.test.ts @@ -124,6 +124,37 @@ describe('Recomposition', () => { }); it('should only read parent xml file once for non-decomposed components with children', async () => { + const component = nonDecomposed.COMPONENT_1; + const readFileSpy = env.spy(component.tree, 'readFile'); + + const context = new ConvertContext(); + context.recomposition.transactionState.set(component.fullName, { + component, + children: new ComponentSet(component.getChildren()), + }); + + const result = await context.recomposition.finalize(); + expect(result).to.deep.equal([ + { + component, + writeInfos: [ + { + source: new JsToXml({ + CustomLabels: { + [XML_NS_KEY]: XML_NS_URL, + labels: [CHILD_1_XML, CHILD_2_XML], + }, + }), + output: join('labels', 'CustomLabels.labels'), + }, + ], + }, + ]); + + expect(readFileSpy.callCount, JSON.stringify(readFileSpy.getCalls(), undefined, 2)).to.equal(0); + }); + + it('should not read parent once already read', async () => { const component = nonDecomposed.COMPONENT_1; const context = new ConvertContext(); context.recomposition.transactionState.set(component.fullName, { @@ -201,18 +232,21 @@ describe('Recomposition', () => { ], }, ]; + const virtualTree = new VirtualTreeContainer(vDir); const component = new SourceComponent( { name: customLabelsType.name, type: customLabelsType, xml: parentXmlPath1 }, - new VirtualTreeContainer(vDir) + virtualTree ); const component2 = new SourceComponent( { name: 'CustomLabels2', type: customLabelsType, xml: parentXmlPath2 }, - new VirtualTreeContainer(vDir) + virtualTree ); it('one main component with multiple parents in transaction state covering all the children', async () => { const context = new ConvertContext(); const compSet = new ComponentSet(); + const readFileSpy = env.spy(virtualTree, 'readFileSync'); + component.getChildren().forEach((child) => compSet.add(child)); component2.getChildren().forEach((child) => compSet.add(child)); context.recomposition.transactionState.set(component.fullName, { @@ -220,7 +254,22 @@ describe('Recomposition', () => { children: compSet, }); - const readFileSpy = env.spy(VirtualTreeContainer.prototype, 'readFile'); + await context.recomposition.finalize(); + + expect(readFileSpy.callCount, 'readFile() should only be called twice').to.equal(2); + }); + + it('once the parent/child file content is cached, it wont read them again', async () => { + const context = new ConvertContext(); + const compSet = new ComponentSet(); + + component.getChildren().forEach((child) => compSet.add(child)); + component2.getChildren().forEach((child) => compSet.add(child)); + context.recomposition.transactionState.set(component.fullName, { + component, + children: compSet, + }); + const readFileSpy = env.spy(virtualTree, 'readFileSync'); await context.recomposition.finalize(); diff --git a/test/resolve/adapters/decomposedSourceAdapter.test.ts b/test/resolve/adapters/decomposedSourceAdapter.test.ts index db7affeb2..38c1063d1 100644 --- a/test/resolve/adapters/decomposedSourceAdapter.test.ts +++ b/test/resolve/adapters/decomposedSourceAdapter.test.ts @@ -12,7 +12,7 @@ import { registry, RegistryAccess, SourceComponent, VirtualTreeContainer } from import { RegistryTestUtil } from '../registryTestUtil'; import { META_XML_SUFFIX } from '../../../src/common'; -describe.skip('DecomposedSourceAdapter', () => { +describe('DecomposedSourceAdapter', () => { const registryAccess = new RegistryAccess(); const type = registry.types.customobject; const tree = new VirtualTreeContainer(decomposed.DECOMPOSED_VIRTUAL_FS); @@ -80,6 +80,8 @@ describe.skip('DecomposedSourceAdapter', () => { assert(decomposed.DECOMPOSED_CHILD_COMPONENT_1.xml); const result = adapter.getComponent(decomposed.DECOMPOSED_CHILD_COMPONENT_1.xml); + // @ts-ignore - this only failed when running 'yarn test' - parent has cache info the result won't + component.parent.pathContentMap = new Map(); expect(result).to.deep.equal(component); }); diff --git a/test/resolve/sourceComponent.test.ts b/test/resolve/sourceComponent.test.ts index 48a4bbd77..d109533f7 100644 --- a/test/resolve/sourceComponent.test.ts +++ b/test/resolve/sourceComponent.test.ts @@ -5,7 +5,7 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ import { join } from 'node:path'; -import { assert, expect } from 'chai'; +import { assert, config, expect } from 'chai'; import { createSandbox } from 'sinon'; import { Messages, SfError } from '@salesforce/core'; import { decomposed, matchingContentFile, mixedContentDirectory, xmlInFolder } from '../mock'; @@ -41,12 +41,14 @@ import { DE_METAFILE } from '../mock/type-constants/digitalExperienceBundleConst import { XML_NS_KEY, XML_NS_URL } from '../../src/common'; import { RegistryTestUtil } from './registryTestUtil'; +config.truncateThreshold = 0; + Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('@salesforce/source-deploy-retrieve', 'sdr'); const env = createSandbox(); -describe.skip('SourceComponent', () => { +describe('SourceComponent', () => { it('should return correct fullName for components without a parent', () => { expect(DECOMPOSED_COMPONENT.fullName).to.equal(DECOMPOSED_COMPONENT.name); });