From 3593de26acd6235042e1ceecf156f1f8a12acc20 Mon Sep 17 00:00:00 2001 From: Nils Andresen Date: Wed, 2 Nov 2022 23:11:17 +0000 Subject: [PATCH] Added some unit tests for navLinkBuilder Because I could not make jest work with any references to SPFx, I separated the navLinkBuilder into its own component. Also, I set collectCoverage to false, as coverage collection is currently broken and I see no easy way to fix this. Two tests are ignored, since they describe the issue and are currently failing. --- samples/react-page-navigator/.node-version | 1 + .../config/jest.config.json | 3 +- .../src/Service/NavLinkBuilder.test.ts | 268 ++++++++++++++++++ .../src/Service/NavLinkBuilder.ts | 37 +++ .../src/Service/SPService.ts | 30 +- .../__snapshots__/NavLinkBuilder.test.ts.snap | 247 ++++++++++++++++ 6 files changed, 557 insertions(+), 29 deletions(-) create mode 100644 samples/react-page-navigator/.node-version create mode 100644 samples/react-page-navigator/src/Service/NavLinkBuilder.test.ts create mode 100644 samples/react-page-navigator/src/Service/NavLinkBuilder.ts create mode 100644 samples/react-page-navigator/src/Service/__snapshots__/NavLinkBuilder.test.ts.snap diff --git a/samples/react-page-navigator/.node-version b/samples/react-page-navigator/.node-version new file mode 100644 index 000000000..fa6bc3a85 --- /dev/null +++ b/samples/react-page-navigator/.node-version @@ -0,0 +1 @@ +v14.20.0 diff --git a/samples/react-page-navigator/config/jest.config.json b/samples/react-page-navigator/config/jest.config.json index cdcc875e8..481bd8482 100644 --- a/samples/react-page-navigator/config/jest.config.json +++ b/samples/react-page-navigator/config/jest.config.json @@ -1,4 +1,5 @@ { "preset": "@voitanos/jest-preset-spfx-react16", - "rootDir": "../src" + "rootDir": "../src", + "collectCoverage": false } \ No newline at end of file diff --git a/samples/react-page-navigator/src/Service/NavLinkBuilder.test.ts b/samples/react-page-navigator/src/Service/NavLinkBuilder.test.ts new file mode 100644 index 000000000..7ce4c773b --- /dev/null +++ b/samples/react-page-navigator/src/Service/NavLinkBuilder.test.ts @@ -0,0 +1,268 @@ +import { IHierarchyEntry, navLinkBuilder } from "./NavLinkBuilder"; + +interface IMockLink extends IHierarchyEntry { + name: string; +} + +const DEPTH_NO_COLLAPSABLE_HEADER = 0; +const DEPTH_COLLAPSABLE_HEADER = 1; + +describe("The NavLinkBuilder without a preceding collapsable header", () => { + + const depth = DEPTH_NO_COLLAPSABLE_HEADER; + const h1 = depth; + const h2 = h1+1; + const h3 = h2+1; + const h4 = h3+1; + + it("should add a single item to an empty list", () => { + const lnk: IMockLink = { + name: "xyz", + }; + + const actual = navLinkBuilder.build([], lnk, h1, depth); + + expect(actual).toMatchSnapshot(); + }); + + it("should add a two items on the same level", () => { + const lnk1: IMockLink = { + name: "xyz", + }; + const lnk2: IMockLink = { + name: "abc", + }; + + let actual = navLinkBuilder.build([], lnk1, h1, depth); + actual = navLinkBuilder.build(actual, lnk2, h1, depth); + + expect(actual).toMatchSnapshot(); + }) + + it("should add a two items on different levels", () => { + const lnk1: IMockLink = { + name: "xyz", + }; + const lnk2: IMockLink = { + name: "abc", + }; + + let actual = navLinkBuilder.build([], lnk1, h1, depth); + actual = navLinkBuilder.build(actual, lnk2, h2, depth); + + expect(actual).toMatchSnapshot(); + }); + + it("should add a two items on the same level and two items on different levels", () => { + const lnk1: IMockLink = { + name: "xyz", + }; + const lnk2: IMockLink = { + name: "abc", + }; + const lnk11: IMockLink = { + name: "xyz.1", + }; + const lnk21: IMockLink = { + name: "abc.1", + }; + + let actual = navLinkBuilder.build([], lnk1, h1, depth); + actual = navLinkBuilder.build(actual, lnk11, h2, depth); + actual = navLinkBuilder.build(actual, lnk2, h1, depth); + actual = navLinkBuilder.build(actual, lnk21, h2, depth); + + expect(actual).toMatchSnapshot(); + }); + + it("should add a four items on different levels", () => { + const lnk1: IMockLink = { + name: "xyz", + }; + const lnk2: IMockLink = { + name: "abc", + }; + const lnk3: IMockLink = { + name: "def", + }; + const lnk4: IMockLink = { + name: "geh", + }; + + let actual = navLinkBuilder.build([], lnk1, h1, depth); + actual = navLinkBuilder.build(actual, lnk2, h2, depth); + actual = navLinkBuilder.build(actual, lnk3, h3, depth); + actual = navLinkBuilder.build(actual, lnk4, h4, depth); + + expect(actual).toMatchSnapshot(); + }); + + it.skip("should not nest two h2", () => { + const lnk1: IMockLink = { + name: "h1", + }; + const lnk2: IMockLink = { + name: "h2", + }; + const lnk3: IMockLink = { + name: "h2", + }; + const lnk4: IMockLink = { + name: "h3", + }; + + let actual = navLinkBuilder.build([], lnk1, h1, depth); + actual = navLinkBuilder.build(actual, lnk2, h2, depth); + actual = navLinkBuilder.build(actual, lnk3, h2, depth); + actual = navLinkBuilder.build(actual, lnk4, h3, depth); + + expect(actual).toMatchSnapshot(); + }); + + it.skip("should not nest two h3", () => { + const lnk0: IMockLink = { + name: "h1", + }; + const lnk1: IMockLink = { + name: "h1", + }; + const lnk2: IMockLink = { + name: "h2", + }; + const lnk3: IMockLink = { + name: "h2", + }; + const lnk4: IMockLink = { + name: "h3", + }; + const lnk5: IMockLink = { + name: "h3", + }; + const lnk6: IMockLink = { + name: "h4", + }; + + let actual = navLinkBuilder.build([], lnk0, h1, depth); + actual = navLinkBuilder.build(actual, lnk1, h1, depth); + actual = navLinkBuilder.build(actual, lnk2, h2, depth); + actual = navLinkBuilder.build(actual, lnk3, h2, depth); + actual = navLinkBuilder.build(actual, lnk4, h3, depth); + actual = navLinkBuilder.build(actual, lnk5, h3, depth); + actual = navLinkBuilder.build(actual, lnk6, h4, depth); + + expect(actual).toMatchSnapshot(); + }); +}); + +describe("The NavLinkBuilder with a collapsable header", () => { + let head: IMockLink; + const depth = DEPTH_COLLAPSABLE_HEADER; + const h1 = depth; + const h2 = h1+1; + const h3 = h2+1; + const h4 = h3+1; + + beforeEach(() => { + head = { + name: "head", + }; + }); + + it("should add a single item to the heading", () => { + const lnk: IMockLink = { + name: "xyz", + }; + + const actual = navLinkBuilder.build([ head ], lnk, h1, depth); + + expect(actual).toMatchSnapshot(); + }); + + it("should add a two items on the same level", () => { + const lnk1: IMockLink = { + name: "xyz", + }; + const lnk2: IMockLink = { + name: "abc", + }; + + let actual = navLinkBuilder.build([ head ], lnk1, h1, depth); + actual = navLinkBuilder.build(actual, lnk2, h1, depth); + + expect(actual).toMatchSnapshot(); + }) + + it("should add a one item in a collapsable section two inside that one", () => { + const lnk1: IMockLink = { + name: "xyz", + }; + const lnk2: IMockLink = { + name: "abc", + }; + const lnk3: IMockLink = { + name: "def", + }; + + let actual = navLinkBuilder.build([ head ], lnk1, h1, depth); + actual = navLinkBuilder.build(actual, lnk2, h2, depth); + actual = navLinkBuilder.build(actual, lnk3, h2, depth); + + expect(actual).toMatchSnapshot(); + }) + + it("should not nest two h2", () => { + const lnk1: IMockLink = { + name: "h1", + }; + const lnk2: IMockLink = { + name: "h2", + }; + const lnk3: IMockLink = { + name: "h2", + }; + const lnk4: IMockLink = { + name: "h3", + }; + + let actual = navLinkBuilder.build([ head ], lnk1, h1, depth); + actual = navLinkBuilder.build(actual, lnk2, h2, depth); + actual = navLinkBuilder.build(actual, lnk3, h2, depth); + actual = navLinkBuilder.build(actual, lnk4, h3, depth); + + expect(actual).toMatchSnapshot(); + }); + + it("should not nest two h3", () => { + const lnk0: IMockLink = { + name: "h1", + }; + const lnk1: IMockLink = { + name: "h1", + }; + const lnk2: IMockLink = { + name: "h2", + }; + const lnk3: IMockLink = { + name: "h2", + }; + const lnk4: IMockLink = { + name: "h3", + }; + const lnk5: IMockLink = { + name: "h3", + }; + const lnk6: IMockLink = { + name: "h4", + }; + + let actual = navLinkBuilder.build([ head ], lnk0, h1, depth); + actual = navLinkBuilder.build(actual, lnk1, h1, depth); + actual = navLinkBuilder.build(actual, lnk2, h2, depth); + actual = navLinkBuilder.build(actual, lnk3, h2, depth); + actual = navLinkBuilder.build(actual, lnk4, h3, depth); + actual = navLinkBuilder.build(actual, lnk5, h3, depth); + actual = navLinkBuilder.build(actual, lnk6, h4, depth); + + expect(actual).toMatchSnapshot(); + }); +}); \ No newline at end of file diff --git a/samples/react-page-navigator/src/Service/NavLinkBuilder.ts b/samples/react-page-navigator/src/Service/NavLinkBuilder.ts new file mode 100644 index 000000000..1d9aaa273 --- /dev/null +++ b/samples/react-page-navigator/src/Service/NavLinkBuilder.ts @@ -0,0 +1,37 @@ +export interface IHierarchyEntry> { + links?: IHierarchyEntry[]; +} + +export class navLinkBuilder { + /** + * Nests a new nav link within the nav links tree + * @param currentLinks current nav links + * @param newLink the new nav link to be added to the structure + * @param order place order of the new link + * @param depth sequence depth + * @returns navLinks + */ + public static build>(currentLinks: T[], newLink: T, order: number, depth: number): T[] { + const lastIndex = currentLinks.length - 1; + + if (lastIndex === -1) { + return [newLink]; + } + + const lastTopLevelLink = currentLinks[lastIndex]; + lastTopLevelLink.links = lastTopLevelLink.links || []; + + if (lastTopLevelLink.links.length === 0 || order === depth) { + if (order !== depth || depth !== 0) { + lastTopLevelLink.links.push(newLink); + } else { + currentLinks.push(newLink); + } + } else { + depth++; + currentLinks[lastIndex].links.concat(this.build(currentLinks[lastIndex].links, newLink, order, depth)); + } + + return currentLinks; + } +} \ No newline at end of file diff --git a/samples/react-page-navigator/src/Service/SPService.ts b/samples/react-page-navigator/src/Service/SPService.ts index 9769dd2d8..e12a1059a 100644 --- a/samples/react-page-navigator/src/Service/SPService.ts +++ b/samples/react-page-navigator/src/Service/SPService.ts @@ -1,6 +1,7 @@ import { INavLink } from 'office-ui-fabric-react/lib/Nav'; import { WebPartContext } from '@microsoft/sp-webpart-base'; import { SPHttpClient } from '@microsoft/sp-http'; +import { navLinkBuilder } from './NavLinkBuilder'; export class SPService { /* Array to store all unique anchor URLs */ @@ -37,33 +38,6 @@ export class SPService { return anchorUrl; } - /** - * Nests a new nav link within the nav links tree - * @param currentLinks current nav links - * @param newLink the new nav link to be added to the structure - * @param order place order of the new link - * @param depth sequence depth - * @returns navLinks - */ - public static navLinkBuilder(currentLinks: INavLink[], newLink: INavLink, order: number, depth: number): INavLink[] { - const lastIndex = currentLinks.length - 1; - - if (lastIndex === -1) { - currentLinks.push(newLink); - } else if (currentLinks[lastIndex].links.length === 0 || order === depth) { - if (order !== depth || depth !== 0) { - currentLinks[lastIndex].links.push(newLink); - } else { - currentLinks.push(newLink); - } - } else { - depth++; - currentLinks[lastIndex].links.concat(this.navLinkBuilder(currentLinks[lastIndex].links, newLink, order, depth)); - } - - return currentLinks; - } - /** * Returns the Anchor Links for Nav element * @param context Web part context @@ -126,7 +100,7 @@ export class SPService { // Add link to nav element const newNavLink: INavLink = { name: headingValue, key: anchorUrl, url: anchorUrl, links: [], isExpanded: true }; - anchorLinks = this.navLinkBuilder(anchorLinks, newNavLink, headingOrder, hasCollapsableHeader ? 1 : 0); + anchorLinks = navLinkBuilder.build(anchorLinks, newNavLink, headingOrder, hasCollapsableHeader ? 1 : 0); }); } }); diff --git a/samples/react-page-navigator/src/Service/__snapshots__/NavLinkBuilder.test.ts.snap b/samples/react-page-navigator/src/Service/__snapshots__/NavLinkBuilder.test.ts.snap new file mode 100644 index 000000000..9707cafe0 --- /dev/null +++ b/samples/react-page-navigator/src/Service/__snapshots__/NavLinkBuilder.test.ts.snap @@ -0,0 +1,247 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`The NavLinkBuilder with a collapsable header should add a one item in a collapsable section two inside that one 1`] = ` +Array [ + Object { + "links": Array [ + Object { + "links": Array [ + Object { + "name": "abc", + }, + Object { + "name": "def", + }, + ], + "name": "xyz", + }, + ], + "name": "head", + }, +] +`; + +exports[`The NavLinkBuilder with a collapsable header should add a single item to the heading 1`] = ` +Array [ + Object { + "links": Array [ + Object { + "name": "xyz", + }, + ], + "name": "head", + }, +] +`; + +exports[`The NavLinkBuilder with a collapsable header should add a two items on the same level 1`] = ` +Array [ + Object { + "links": Array [ + Object { + "name": "xyz", + }, + Object { + "name": "abc", + }, + ], + "name": "head", + }, +] +`; + +exports[`The NavLinkBuilder with a collapsable header should not nest two h2 1`] = ` +Array [ + Object { + "links": Array [ + Object { + "links": Array [ + Object { + "name": "h2", + }, + Object { + "links": Array [ + Object { + "name": "h3", + }, + ], + "name": "h2", + }, + ], + "name": "h1", + }, + ], + "name": "head", + }, +] +`; + +exports[`The NavLinkBuilder with a collapsable header should not nest two h3 1`] = ` +Array [ + Object { + "links": Array [ + Object { + "name": "h1", + }, + Object { + "links": Array [ + Object { + "name": "h2", + }, + Object { + "links": Array [ + Object { + "name": "h3", + }, + Object { + "links": Array [ + Object { + "name": "h4", + }, + ], + "name": "h3", + }, + ], + "name": "h2", + }, + ], + "name": "h1", + }, + ], + "name": "head", + }, +] +`; + +exports[`The NavLinkBuilder without a preceding collapsable header should add a four items on different levels 1`] = ` +Array [ + Object { + "links": Array [ + Object { + "links": Array [ + Object { + "links": Array [ + Object { + "name": "geh", + }, + ], + "name": "def", + }, + ], + "name": "abc", + }, + ], + "name": "xyz", + }, +] +`; + +exports[`The NavLinkBuilder without a preceding collapsable header should add a single item to an empty list 1`] = ` +Array [ + Object { + "name": "xyz", + }, +] +`; + +exports[`The NavLinkBuilder without a preceding collapsable header should add a two items on different levels 1`] = ` +Array [ + Object { + "links": Array [ + Object { + "name": "abc", + }, + ], + "name": "xyz", + }, +] +`; + +exports[`The NavLinkBuilder without a preceding collapsable header should add a two items on the same level 1`] = ` +Array [ + Object { + "links": Array [], + "name": "xyz", + }, + Object { + "name": "abc", + }, +] +`; + +exports[`The NavLinkBuilder without a preceding collapsable header should add a two items on the same level and two items on different levels 1`] = ` +Array [ + Object { + "links": Array [ + Object { + "name": "xyz.1", + }, + ], + "name": "xyz", + }, + Object { + "links": Array [ + Object { + "name": "abc.1", + }, + ], + "name": "abc", + }, +] +`; + +exports[`The NavLinkBuilder without a preceding collapsable header should not nest two h2 1`] = ` +Array [ + Object { + "links": Array [ + Object { + "links": Array [], + "name": "h2", + }, + Object { + "links": Array [ + Object { + "name": "h3", + }, + ], + "name": "h2", + }, + ], + "name": "h1", + }, +] +`; + +exports[`The NavLinkBuilder without a preceding collapsable header should not nest two h3 1`] = ` +Array [ + Object { + "links": Array [], + "name": "h1", + }, + Object { + "links": Array [ + Object { + "links": Array[], + "name": "h2", + }, + Object { + "links": Array [ + Object { + "name": "h3", + }, + Object { + "links": Array [ + Object { + "name": "h4", + }, + ], + "name": "h3", + }, + ], + "name": "h2", + }, + ], + "name": "h1", + }, +] +`;