Skip to content

Commit

Permalink
feat: add test for build docs script (#3137)
Browse files Browse the repository at this point in the history
Co-authored-by: Ansh Goyal <[email protected]>
  • Loading branch information
vishvamsinh28 and anshgoyalevil authored Oct 7, 2024
1 parent d070b2e commit 662ec26
Show file tree
Hide file tree
Showing 7 changed files with 516 additions and 95 deletions.
208 changes: 113 additions & 95 deletions scripts/build-docs.js
Original file line number Diff line number Diff line change
@@ -1,164 +1,182 @@
const sortBy = require('lodash/sortBy')
function buildNavTree(navItems) {
try {
const tree = {
'welcome': {
item: { title: 'Welcome', weight: 0, isRootSection: true, isSection: true, rootSectionId: 'welcome', sectionWeight: 0, slug: '/docs' },
children: {}
}
}

//first we make sure that list of items lists main section items and then sub sections, documents last
const sortedItems = sortBy(navItems, ['isRootSection', 'weight', 'isSection']);
const sortedItems = sortBy(navItems, ['isRootSection', 'weight', 'isSection']);

sortedItems.forEach(item => {
//identify main sections
if (item.isRootSection) {
tree[item.rootSectionId] = { item, children: {} }
}

//identify subsections
if (item.parent) {
tree[item.parent].children[item.sectionId] = { item, children: [] }
if (!tree[item.parent]) {
throw new Error(`Parent section ${item.parent} not found for item ${item.title}`);
}
tree[item.parent].children[item.sectionId] = { item, children: [] };
}

if (!item.isSection) {
if (item.sectionId) {
let section = tree[item.rootSectionId].children[item.sectionId];
let section = tree[item.rootSectionId]?.children[item.sectionId];
if (!section) {
tree[item.rootSectionId].children[item.sectionId] = { item, children: [] }
tree[item.rootSectionId].children[item.sectionId] = { item, children: [] };
}
tree[item.rootSectionId].children[item.sectionId].children.push(item)
tree[item.rootSectionId].children[item.sectionId].children.push(item);
} else {
tree[item.rootSectionId].children[item.title] = { item };
}
}
})
});

for (const [rootKey, rootValue] of Object.entries(tree)) {
const allChildren = rootValue.children;
const allChildrenKeys = Object.keys(allChildren);

rootValue.children = allChildrenKeys
.sort((prev, next) => {
return allChildren[prev].item.weight - allChildren[next].item.weight;
}).reduce(
(obj, key) => {
obj[key] = allChildren[key];
return obj;
},
{}
);

})
.reduce((obj, key) => {
obj[key] = allChildren[key];
return obj;
}, {});

//handling subsections
if (allChildrenKeys.length > 1) {
for (const key of allChildrenKeys) {
allChildren[key].children?.sort((prev, next) => {
return prev.weight - next.weight;
});

if (allChildren[key].children) {
allChildren[key].children.sort((prev, next) => {
return prev.weight - next.weight;
});
}

// point in slug for specification subgroup to the latest specification version
if (rootKey === 'reference' && key === 'specification') {
allChildren[key].item.href = allChildren[key].children.find(c => c.isPrerelease === undefined).slug;
}
}
}
}

return tree;

} catch (err) {
throw new Error(`Failed to build navigation tree: ${err.message}`);
}
}

// A recursion function, works on the logic of Depth First Search to traverse all the root and child posts of the
// DocTree to get sequential order of the Doc Posts
// A recursion function, works on the logic of Depth First Search to traverse all the root and child posts of the
// DocTree to get sequential order of the Doc Posts
const convertDocPosts = (docObject) => {
try {
let docsArray = []
// certain entries in the DocPosts are either a parent to many posts or itself a post.
docsArray.push(docObject?.item || docObject)
if(docObject.children){
docsArray.push(docObject?.item || docObject)
if (docObject.children) {
let children = docObject.children
Object.keys(children).forEach((child) => {
let docChildArray = convertDocPosts(children[child])
docsArray = [...docsArray, ...docChildArray]
})
}
return docsArray
}
catch (err) {
throw new Error('Error in convertDocPosts:', err);
}
}


function addDocButtons(docPosts, treePosts){

function addDocButtons(docPosts, treePosts) {
let structuredPosts = [];
let rootSections = [];

// Traversing the whole DocTree and storing each post inside them in sequential order
Object.keys(treePosts).forEach((rootElement) => {
structuredPosts.push(treePosts[rootElement].item)
if(treePosts[rootElement].children){
let children = treePosts[rootElement].children
Object.keys(children).forEach((child) => {
let docChildArray = convertDocPosts(children[child])
structuredPosts = [...structuredPosts, ...docChildArray]
})
}
})
// Appending the content of welcome page pf Docs from the posts.json
structuredPosts[0] = docPosts.filter(p => p.slug === '/docs')[0]

// Traversing the strucutredPosts in order to add `nextPage` and `prevPage` details for each page
let countDocPages = structuredPosts.length
structuredPosts = structuredPosts.map((post, index) => {
// post item specifying the root Section or sub-section in the docs are excluded as
// they doesn't comprise any Doc Page or content to be shown in website.
if(post?.isRootSection || post?.isSection || index==0){
if(post?.isRootSection || index==0)
rootSections.push(post.title)
return post
}
try {
// Traversing the whole DocTree and storing each post inside them in sequential order
Object.keys(treePosts).forEach((rootElement) => {
structuredPosts.push(treePosts[rootElement].item);
if (treePosts[rootElement].children) {
let children = treePosts[rootElement].children;
Object.keys(children).forEach((child) => {
let docChildArray = convertDocPosts(children[child]);
structuredPosts = [...structuredPosts, ...docChildArray];
});
}
});

let nextPage = {}, prevPage = {}
let docPost = post;

// checks whether the next page for the current docPost item exists or not
if(index+1<countDocPages){
// checks whether the next item inside structuredPosts is a rootElement or a sectionElement
// if yes, it goes again to a next to next item in structuredPosts to link the nextPage
if(!structuredPosts[index+1].isRootElement && !structuredPosts[index+1].isSection){
nextPage = {
title: structuredPosts[index+1].title,
href: structuredPosts[index+1].slug
}
} else {
nextPage = {
title: `${structuredPosts[index+1].title} - ${structuredPosts[index+2].title}`,
href: structuredPosts[index+2].slug
}
// Appending the content of welcome page of Docs from the posts.json
structuredPosts[0] = docPosts.filter(p => p.slug === '/docs')[0];

// Traversing the structuredPosts in order to add `nextPage` and `prevPage` details for each page
let countDocPages = structuredPosts.length;
structuredPosts = structuredPosts.map((post, index) => {
// post item specifying the root Section or sub-section in the docs are excluded as
// they doesn't comprise any Doc Page or content to be shown in website.
if (post?.isRootSection || post?.isSection || index == 0) {
if (post?.isRootSection || index == 0)
rootSections.push(post.title)
return post
}
docPost = {...docPost, nextPage}
}

// checks whether the previous page for the current docPost item exists or not
if(index>0){
// checks whether the previous item inside structuredPosts is a rootElement or a sectionElement
// if yes, it goes again to a next previous item in structuredPosts to link the prevPage
if(!structuredPosts[index-1]?.isRootElement && !structuredPosts[index-1]?.isSection){
prevPage = {
title: structuredPosts[index-1].title,
href: structuredPosts[index-1].slug
let nextPage = {}, prevPage = {}
let docPost = post;

// checks whether the next page for the current docPost item exists or not
if (index + 1 < countDocPages) {
// checks whether the next item inside structuredPosts is a rootElement or a sectionElement
// if yes, it goes again to a next to next item in structuredPosts to link the nextPage
if (!structuredPosts[index + 1].isRootElement && !structuredPosts[index + 1].isSection) {
nextPage = {
title: structuredPosts[index + 1].title,
href: structuredPosts[index + 1].slug
}
} else {
nextPage = {
title: `${structuredPosts[index + 1].title} - ${structuredPosts[index + 2].title}`,
href: structuredPosts[index + 2].slug
}
}
docPost = {...docPost, prevPage}
}else{
// additonal check for the first page of Docs so that it doesn't give any Segementation fault
if(index-2>=0){
docPost = { ...docPost, nextPage }
}

// checks whether the previous page for the current docPost item exists or not
if (index > 0) {
// checks whether the previous item inside structuredPosts is a rootElement or a sectionElement
// if yes, it goes again to a next previous item in structuredPosts to link the prevPage
if (!structuredPosts[index - 1]?.isRootElement && !structuredPosts[index - 1]?.isSection) {
prevPage = {
title: `${structuredPosts[index-1]?.isRootSection ? rootSections[rootSections.length - 2] : rootSections[rootSections.length - 1]} - ${structuredPosts[index-2].title}`,
href: structuredPosts[index-2].slug
title: structuredPosts[index - 1].title,
href: structuredPosts[index - 1].slug
}
docPost = { ...docPost, prevPage }
} else {
// additonal check for the first page of Docs so that it doesn't give any Segementation fault
if (index - 2 >= 0) {
prevPage = {
title: `${structuredPosts[index - 1]?.isRootSection ? rootSections[rootSections.length - 2] : rootSections[rootSections.length - 1]} - ${structuredPosts[index - 2].title}`,
href: structuredPosts[index - 2].slug
};
docPost = { ...docPost, prevPage };
}
docPost = {...docPost, prevPage}
}
}
}
return docPost
})
return structuredPosts
}
return docPost;
});

} catch (err) {
throw new Error("An error occurred while adding doc buttons:", err);
}
return structuredPosts;
}

module.exports = {buildNavTree, addDocButtons, convertDocPosts}
module.exports = { buildNavTree, addDocButtons, convertDocPosts }
91 changes: 91 additions & 0 deletions tests/build-docs/addDocButtons.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
const { addDocButtons } = require("../../scripts/build-docs");
const { docPosts, treePosts, mockDocPosts, mockTreePosts, invalidTreePosts } = require("../fixtures/addDocButtonsData");

describe('addDocButtons', () => {
it('should add next and previous page information', () => {
const expectedFirstItem = {
title: 'Welcome',
slug: '/docs',
content: 'Welcome content'
};

const expectedSecondItem = {
isRootSection: true,
title: 'Section 1'
};

const expectedThirdItem = {
title: 'Page 1',
slug: '/docs/section1/page1',
nextPage: {
title: 'Page 2',
href: '/docs/section1/page2'
},
prevPage: {
title: 'Section 1',
href: undefined
}
};

const expectedFourthItem = {
title: 'Page 2',
slug: '/docs/section1/page2',
prevPage: {
title: 'Page 1',
href: '/docs/section1/page1'
}
};

const result = addDocButtons(docPosts, treePosts);

expect(result).toHaveLength(4);
expect(result[0]).toEqual(expectedFirstItem);
expect(result[1]).toEqual(expectedSecondItem);
expect(result[2]).toEqual(expectedThirdItem);
expect(result[3]).toEqual(expectedFourthItem);
});

it('should set nextPage correctly when next item is a root element', () => {
const result = addDocButtons(mockDocPosts, mockTreePosts);

expect(result[1].nextPage).toBeDefined();
expect(result[1].nextPage.title).toBe('Root 2 - Child 2');
expect(result[1].nextPage.href).toBe('/docs/root2/child2');
});

it('should throw an error if treePosts is missing', () => {
let error;

try {
addDocButtons(docPosts, undefined);
} catch (err) {
error = err
expect(err.message).toContain("An error occurred while adding doc buttons:");
}
expect(error).toBeDefined()
});

it('should throw an error if docPosts is missing', () => {
let error;

try {
addDocButtons(undefined, treePosts);
} catch (err) {
error = err
expect(err.message).toContain("An error occurred while adding doc buttons:");
}
expect(error).toBeDefined()
});

it('should handle invalid data structure in treePosts', () => {
let error;

try {
addDocButtons(docPosts, invalidTreePosts);
} catch (err) {
error = err;
expect(err.message).toContain("An error occurred while adding doc buttons:");
}
expect(error).toBeDefined()
});
});
Loading

0 comments on commit 662ec26

Please sign in to comment.