Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 26 additions & 14 deletions src/utils/navigatorData.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,19 @@ export function getSiblings(uid, childrenMap, children) {
return getChildren(item.parent, childrenMap, children);
}

function extractRootNode(data) {
/**
* 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
*/
export function flattenModules(modules) {
return modules.flatMap(module => [
module, ...flattenModules((module.children || []).filter(child => child.type === 'module')),
]);
}

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
// prefix
Expand All @@ -192,9 +204,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 flattenedModules.length === 1 ? flattenedModules[0] : (flattenedModules.find(module => (
module.path.toLowerCase().endsWith(rootPath.toLowerCase())
)) ?? flattenedModules[0]);
}

/**
Expand All @@ -203,11 +215,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 rootModule = extractRootModule(modules);
acc[language] = flattenNestedData(
topLevelNode.children || [], null, 0, topLevelNode.beta,
rootModule.children || [], null, 0, rootModule.beta,
);
return acc;
}, {});
Expand All @@ -217,13 +229,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 rootModule = extractRootModule(modules);
acc[language] = {
technology: topLevelNode.title,
technologyPath: topLevelNode.path || topLevelNode.url,
isTechnologyBeta: topLevelNode.beta,
technology: rootModule.title,
technologyPath: rootModule.path || rootModule.url,
isTechnologyBeta: rootModule.beta,
};
return acc;
}, {});
Expand Down
166 changes: 166 additions & 0 deletions tests/unit/utils/navigatorData.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import {
convertChildrenArrayToObject,
extractTechnologyProps,
flattenModules,
flattenNavigationIndex,
flattenNestedData,
getAllChildren,
Expand Down Expand Up @@ -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');
});
});