-
-
Notifications
You must be signed in to change notification settings - Fork 696
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add test for build docs script (#3137)
Co-authored-by: Ansh Goyal <[email protected]>
- Loading branch information
1 parent
d070b2e
commit 662ec26
Showing
7 changed files
with
516 additions
and
95 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
}); | ||
}); |
Oops, something went wrong.