From 8716fd04f53c88cce3f1a082037088c1bbf70073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marina=20A=C3=ADsa?= Date: Mon, 1 Dec 2025 16:11:24 +0000 Subject: [PATCH 1/5] Rename langData as modules It's confusing to use `landData` and `data` when we are referring to `modules` in the navigator instead. This change replace `landData` and `data` with `modules`. It also replace `node` with `module`. This change will help us to identify the modules that we need to flat in future commits. --- src/utils/navigatorData.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/utils/navigatorData.js b/src/utils/navigatorData.js index 3ef902063..63aa665c0 100644 --- a/src/utils/navigatorData.js +++ b/src/utils/navigatorData.js @@ -178,7 +178,7 @@ export function getSiblings(uid, childrenMap, children) { return getChildren(item.parent, childrenMap, children); } -function extractRootNode(data) { +function extractRootNode(modules) { // note: this "root" path won't always necessarily come at the beginning of // the URL in situations where the renderer is being hosted at some path // prefix @@ -192,9 +192,9 @@ function extractRootNode(data) { // with a path that most closely resembles the current URL path // // otherwise, the first provided node will be used - return data.length === 1 ? data[0] : (data.find(node => ( - node.path.toLowerCase().endsWith(rootPath.toLowerCase()) - )) ?? data[0]); + return modules.length === 1 ? modules[0] : (modules.find(module => ( + module.path.toLowerCase().endsWith(rootPath.toLowerCase()) + )) ?? modules[0]); } /** @@ -203,11 +203,11 @@ function extractRootNode(data) { * @return { languageVariant: NavigatorFlatItem[] } */ export function flattenNavigationIndex(languages) { - return Object.entries(languages).reduce((acc, [language, langData]) => { - if (!langData.length) return acc; - const topLevelNode = extractRootNode(langData); + return Object.entries(languages).reduce((acc, [language, modules]) => { + if (!modules.length) return acc; + const topLevelModule = extractRootNode(modules); acc[language] = flattenNestedData( - topLevelNode.children || [], null, 0, topLevelNode.beta, + topLevelModule.children || [], null, 0, topLevelModule.beta, ); return acc; }, {}); @@ -217,13 +217,13 @@ export function flattenNavigationIndex(languages) { * Extract technology data for each language variant */ export function extractTechnologyProps(indexData) { - return Object.entries(indexData).reduce((acc, [language, langData]) => { - if (!langData.length) return acc; - const topLevelNode = extractRootNode(langData); + return Object.entries(indexData).reduce((acc, [language, modules]) => { + if (!modules.length) return acc; + const topLevelModule = extractRootNode(modules); acc[language] = { - technology: topLevelNode.title, - technologyPath: topLevelNode.path || topLevelNode.url, - isTechnologyBeta: topLevelNode.beta, + technology: topLevelModule.title, + technologyPath: topLevelModule.path || topLevelModule.url, + isTechnologyBeta: topLevelModule.beta, }; return acc; }, {}); From 6e40ec987602bcee2f7d51f15d4c0efd99b93954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marina=20A=C3=ADsa?= Date: Mon, 1 Dec 2025 16:21:41 +0000 Subject: [PATCH 2/5] Flatten modules In some cases, our navigator can have nested modules within the top level modules. We want to extract all of them to be able to identify which module we want to use. This change introduces a recursively function that flatten a nested module structure into a single array. So we can identify which it's the top level module. --- src/utils/navigatorData.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/utils/navigatorData.js b/src/utils/navigatorData.js index 63aa665c0..f4f2c10f4 100644 --- a/src/utils/navigatorData.js +++ b/src/utils/navigatorData.js @@ -178,7 +178,19 @@ export function getSiblings(uid, childrenMap, children) { return getChildren(item.parent, childrenMap, children); } +/** + * Recursively flatten a nested module structure into a single array + * @param {Object[]} modules - Array of module objects with optional children + * @return {Object[]} Flattened array containing all modules and their nested module children + */ +function flattenModules(modules) { + return modules.flatMap(module => [ + module, ...flattenModules((module.children || []).filter(child => child.type === 'module')), + ]); +} + function extractRootNode(modules) { + const flattenedModules = flattenModules(modules); // note: this "root" path won't always necessarily come at the beginning of // the URL in situations where the renderer is being hosted at some path // prefix @@ -192,9 +204,9 @@ function extractRootNode(modules) { // with a path that most closely resembles the current URL path // // otherwise, the first provided node will be used - return modules.length === 1 ? modules[0] : (modules.find(module => ( + return flattenedModules.length === 1 ? flattenedModules[0] : (flattenedModules.find(module => ( module.path.toLowerCase().endsWith(rootPath.toLowerCase()) - )) ?? modules[0]); + )) ?? flattenedModules[0]); } /** From ef6ca746a2c7d9ed4788a325f5557855728e6a43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marina=20A=C3=ADsa?= Date: Mon, 1 Dec 2025 16:33:19 +0000 Subject: [PATCH 3/5] Rename extractRootNode with extractRootModule To be consistent with previous changes, we want to rename this function. --- src/utils/navigatorData.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/navigatorData.js b/src/utils/navigatorData.js index f4f2c10f4..99b48ccfb 100644 --- a/src/utils/navigatorData.js +++ b/src/utils/navigatorData.js @@ -189,7 +189,7 @@ function flattenModules(modules) { ]); } -function extractRootNode(modules) { +function extractRootModule(modules) { const flattenedModules = flattenModules(modules); // note: this "root" path won't always necessarily come at the beginning of // the URL in situations where the renderer is being hosted at some path @@ -217,7 +217,7 @@ function extractRootNode(modules) { export function flattenNavigationIndex(languages) { return Object.entries(languages).reduce((acc, [language, modules]) => { if (!modules.length) return acc; - const topLevelModule = extractRootNode(modules); + const topLevelModule = extractRootModule(modules); acc[language] = flattenNestedData( topLevelModule.children || [], null, 0, topLevelModule.beta, ); @@ -231,7 +231,7 @@ export function flattenNavigationIndex(languages) { export function extractTechnologyProps(indexData) { return Object.entries(indexData).reduce((acc, [language, modules]) => { if (!modules.length) return acc; - const topLevelModule = extractRootNode(modules); + const topLevelModule = extractRootModule(modules); acc[language] = { technology: topLevelModule.title, technologyPath: topLevelModule.path || topLevelModule.url, From c035d3d7a09eab9727fed1ba8ac50638b26fc743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marina=20A=C3=ADsa?= Date: Mon, 1 Dec 2025 16:36:46 +0000 Subject: [PATCH 4/5] Rename rootModule Now that we aren't always extracting the top level modules, but the root module for the navigator. We want to replace the name of topLevelModule with rootModule. --- src/utils/navigatorData.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/utils/navigatorData.js b/src/utils/navigatorData.js index 99b48ccfb..c7a8157c9 100644 --- a/src/utils/navigatorData.js +++ b/src/utils/navigatorData.js @@ -217,9 +217,9 @@ function extractRootModule(modules) { export function flattenNavigationIndex(languages) { return Object.entries(languages).reduce((acc, [language, modules]) => { if (!modules.length) return acc; - const topLevelModule = extractRootModule(modules); + const rootModule = extractRootModule(modules); acc[language] = flattenNestedData( - topLevelModule.children || [], null, 0, topLevelModule.beta, + rootModule.children || [], null, 0, rootModule.beta, ); return acc; }, {}); @@ -231,11 +231,11 @@ export function flattenNavigationIndex(languages) { export function extractTechnologyProps(indexData) { return Object.entries(indexData).reduce((acc, [language, modules]) => { if (!modules.length) return acc; - const topLevelModule = extractRootModule(modules); + const rootModule = extractRootModule(modules); acc[language] = { - technology: topLevelModule.title, - technologyPath: topLevelModule.path || topLevelModule.url, - isTechnologyBeta: topLevelModule.beta, + technology: rootModule.title, + technologyPath: rootModule.path || rootModule.url, + isTechnologyBeta: rootModule.beta, }; return acc; }, {}); From 42100d7d9762e2702e174a667878883c9c97f27d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marina=20A=C3=ADsa?= Date: Mon, 1 Dec 2025 17:27:31 +0000 Subject: [PATCH 5/5] Add unit test to the flattenModules function --- src/utils/navigatorData.js | 2 +- tests/unit/utils/navigatorData.spec.js | 166 +++++++++++++++++++++++++ 2 files changed, 167 insertions(+), 1 deletion(-) diff --git a/src/utils/navigatorData.js b/src/utils/navigatorData.js index c7a8157c9..b2aa491ad 100644 --- a/src/utils/navigatorData.js +++ b/src/utils/navigatorData.js @@ -183,7 +183,7 @@ export function getSiblings(uid, childrenMap, children) { * @param {Object[]} modules - Array of module objects with optional children * @return {Object[]} Flattened array containing all modules and their nested module children */ -function flattenModules(modules) { +export function flattenModules(modules) { return modules.flatMap(module => [ module, ...flattenModules((module.children || []).filter(child => child.type === 'module')), ]); diff --git a/tests/unit/utils/navigatorData.spec.js b/tests/unit/utils/navigatorData.spec.js index 014b1c83d..bfcd28b9a 100644 --- a/tests/unit/utils/navigatorData.spec.js +++ b/tests/unit/utils/navigatorData.spec.js @@ -11,6 +11,7 @@ import { convertChildrenArrayToObject, extractTechnologyProps, + flattenModules, flattenNavigationIndex, flattenNestedData, getAllChildren, @@ -436,3 +437,168 @@ describe('when multiple top-level children are provided', () => { }); }); }); + +describe('flattenModules', () => { + it('flattens nested modules while preserving root modules', () => { + const modules = [ + { + path: '/documentation/testkit', + title: 'Testkit', + type: 'module', + children: [], + }, + { + path: '/documentation/foo', + title: 'Foo', + type: 'module', + children: [ + { + path: '/documentation/nestedmodule', + title: 'NestedModule', + type: 'module', + children: [], + }, + ], + }, + ]; + + const result = flattenModules(modules); + + expect(result).toEqual([ + { + path: '/documentation/testkit', + title: 'Testkit', + type: 'module', + children: [], + }, + { + path: '/documentation/foo', + title: 'Foo', + type: 'module', + children: [ + { + path: '/documentation/nestedmodule', + title: 'NestedModule', + type: 'module', + children: [], + }, + ], + }, + { + path: '/documentation/nestedmodule', + title: 'NestedModule', + type: 'module', + children: [], + }, + ]); + }); + + it('filters out non-module children when flattening', () => { + const modules = [ + { + path: '/documentation/foo', + title: 'Foo', + type: 'module', + children: [ + { + path: '/documentation/foo/article', + title: 'Foo Article', + type: 'article', + }, + { + path: '/documentation/nestedmodule', + title: 'NestedModule', + type: 'module', + }, + { + path: '/documentation/foo/tutorial', + title: 'Foo Tutorial', + type: 'tutorial', + }, + ], + }, + ]; + + const result = flattenModules(modules); + + expect(result).toEqual([ + { + path: '/documentation/foo', + title: 'Foo', + type: 'module', + children: [ + { + path: '/documentation/foo/article', + title: 'Foo Article', + type: 'article', + }, + { + path: '/documentation/nestedmodule', + title: 'NestedModule', + type: 'module', + }, + { + path: '/documentation/foo/tutorial', + title: 'Foo Tutorial', + type: 'tutorial', + }, + ], + }, + { + path: '/documentation/nestedmodule', + title: 'NestedModule', + type: 'module', + }, + ]); + }); + + it('extracts root module correctly from interfaceLanguages data structure', () => { + const interfaceLanguages = { + occ: [], + swift: [ + { + children: [ + { + path: '/documentation/foo/article', + title: 'Foo Article', + type: 'article', + }, + ], + path: '/documentation/testkit', + title: 'Testkit', + type: 'module', + }, + { + children: [ + { + children: [ + { + path: '/documentation/nestedmodule/submodule', + title: 'SubModule', + type: 'module', + }, + ], + path: '/documentation/nestedmodule', + title: 'NestedModule', + type: 'module', + }, + ], + path: '/documentation/foo', + title: 'Foo', + type: 'module', + }, + ], + }; + + const result = flattenModules(interfaceLanguages.swift); + + // Should include all modules: Testkit, Foo, NestedModule, SubModule + expect(result).toHaveLength(4); + + const modulePaths = result.map(module => module.path); + expect(modulePaths).toContain('/documentation/testkit'); + expect(modulePaths).toContain('/documentation/foo'); + expect(modulePaths).toContain('/documentation/nestedmodule'); + expect(modulePaths).toContain('/documentation/nestedmodule/submodule'); + }); +});