diff --git a/src/features/preferences/__snapshots__/hiding-of-empty-branches.test.tsx.snap b/src/features/preferences/__snapshots__/hiding-of-empty-branches.test.tsx.snap new file mode 100644 index 0000000000..320b19728f --- /dev/null +++ b/src/features/preferences/__snapshots__/hiding-of-empty-branches.test.tsx.snap @@ -0,0 +1,2020 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`preferences - hiding-of-empty-branches, given in preferences page given tab group and empty tabs renders 1`] = ` +
+
+
+
+
+ + + home + + +
+
+
+ + + arrow_back + + +
+
+
+ + + arrow_forward + + +
+
+
+
+
+
+
+ +
+
+
+

+ Application +

+
+
+ Theme + +
+
+ + +
+
+
+ Select... +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+ Extension Install Registry + +
+
+ + +
+
+
+ Select... +
+
+ +
+
+
+ + +
+
+
+

+ This setting is to change the registry URL for installing extensions by name. + If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your + + .npmrc + + file or in the input below. +

+
+ +
+
+
+
+
+
+ Start-up + +
+ +
+
+
+
+ Update Channel + +
+
+ + +
+
+
+ Stable +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+ Locale Timezone + +
+
+ + +
+
+
+ Select... +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+
+
+
+ + + close + + +
+ +
+
+
+
+
+
+
+
+
+ + + arrow_left + + +
+
+ 0 +
+
+ + + arrow_right + + +
+
+
+
+
+
+
+
+
+`; + +exports[`preferences - hiding-of-empty-branches, given in preferences page given tab group and empty tabs when an item appears for one of the tabs renders 1`] = ` +
+
+
+
+
+ + + home + + +
+
+
+ + + arrow_back + + +
+
+
+ + + arrow_forward + + +
+
+
+
+
+
+
+ +
+
+
+

+ Application +

+
+
+ Theme + +
+
+ + +
+
+
+ Select... +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+ Extension Install Registry + +
+
+ + +
+
+
+ Select... +
+
+ +
+
+
+ + +
+
+
+

+ This setting is to change the registry URL for installing extensions by name. + If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your + + .npmrc + + file or in the input below. +

+
+ +
+
+
+
+
+
+ Start-up + +
+ +
+
+
+
+ Update Channel + +
+
+ + +
+
+
+ Stable +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+ Locale Timezone + +
+
+ + +
+
+
+ Select... +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+
+
+
+ + + close + + +
+ +
+
+
+
+
+
+
+
+
+ + + arrow_left + + +
+
+ 0 +
+
+ + + arrow_right + + +
+
+
+
+
+
+
+
+
+`; + +exports[`preferences - hiding-of-empty-branches, given in preferences page given tab group and empty tabs when an item appears for one of the tabs when an item appears for the remaining tab renders 1`] = ` +
+
+
+
+
+ + + home + + +
+
+
+ + + arrow_back + + +
+
+
+ + + arrow_forward + + +
+
+
+
+
+
+
+ +
+
+
+

+ Application +

+
+
+ Theme + +
+
+ + +
+
+
+ Select... +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+ Extension Install Registry + +
+
+ + +
+
+
+ Select... +
+
+ +
+
+
+ + +
+
+
+

+ This setting is to change the registry URL for installing extensions by name. + If you are unable to access the default registry (https://registry.npmjs.org) you can change it in your + + .npmrc + + file or in the input below. +

+
+ +
+
+
+
+
+
+ Start-up + +
+ +
+
+
+
+ Update Channel + +
+
+ + +
+
+
+ Stable +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+ Locale Timezone + +
+
+ + +
+
+
+ Select... +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+
+
+
+ + + close + + +
+ +
+
+
+
+
+
+
+
+
+ + + arrow_left + + +
+
+ 0 +
+
+ + + arrow_right + + +
+
+
+
+
+
+
+
+
+`; diff --git a/src/features/preferences/hiding-of-empty-branches.test.tsx b/src/features/preferences/hiding-of-empty-branches.test.tsx new file mode 100644 index 0000000000..9c36b1eaab --- /dev/null +++ b/src/features/preferences/hiding-of-empty-branches.test.tsx @@ -0,0 +1,194 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import React from "react"; +import type { DiContainer } from "@ogre-tools/injectable"; +import { getInjectable } from "@ogre-tools/injectable"; +import type { RenderResult } from "@testing-library/react"; +import { runInAction } from "mobx"; +import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; +import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; +import { preferenceItemInjectionToken } from "./renderer/preference-items/preference-item-injection-token"; + +describe("preferences - hiding-of-empty-branches, given in preferences page", () => { + let builder: ApplicationBuilder; + let rendered: RenderResult; + let windowDi: DiContainer; + + beforeEach(async () => { + builder = getApplicationBuilder(); + + rendered = await builder.render(); + + builder.preferences.navigate(); + + windowDi = builder.applicationWindow.only.di; + }); + + describe("given tab group and empty tabs", () => { + beforeEach(() => { + const someTabGroupInjectable = getInjectable({ + id: "some-tab-group", + + instantiate: () => ({ + kind: "tab-group" as const, + id: "some-tab-group", + testId: "some-tab-group-test-id", + parentId: "preference-tabs" as const, + label: "Some tab group label", + orderNumber: 10, + }), + + injectionToken: preferenceItemInjectionToken, + }); + + const tabWithItemsInjectable = getInjectable({ + id: "some-tab", + + instantiate: () => ({ + kind: "tab" as const, + id: "some-tab-with-items-id", + parentId: "some-tab-group" as const, + testId: "some-tab-with-items", + pathId: "irrelevant", + label: "Some label for tab with items", + orderNumber: 10, + }), + + injectionToken: preferenceItemInjectionToken, + }); + + const tabWithoutItemsInjectable = getInjectable({ + id: "some-empty-tab", + + instantiate: () => ({ + kind: "tab" as const, + id: "some-tab-without-items-id", + parentId: "some-tab-group" as const, + testId: "some-tab-without-items", + pathId: "irrelevant", + label: "Some label for tab without items", + orderNumber: 10, + }), + + injectionToken: preferenceItemInjectionToken, + }); + + runInAction(() => { + windowDi.register( + someTabGroupInjectable, + tabWithItemsInjectable, + tabWithoutItemsInjectable, + ); + }); + }); + + it("renders", () => { + expect(rendered.container).toMatchSnapshot(); + }); + + it("does not render the empty tab group", () => { + const someTabGroup = rendered.queryByTestId("some-tab-group-test-id"); + + expect(someTabGroup).toBeNull(); + }); + + it("does not render the empty tabs", () => { + const someTab = rendered.queryByTestId("some-tab-with-items"); + const someOtherTab = rendered.queryByTestId("some-tab-without-items"); + + expect([someTab, someOtherTab]).toEqual([null, null]); + }); + + describe("when an item appears for one of the tabs", () => { + beforeEach(() => { + const itemForTabInjectable = getInjectable({ + id: "some-preference-item", + + instantiate: () => ({ + kind: "item" as const, + id: "some-preference-item-id", + parentId: "some-tab-with-items-id" as const, + testId: "some-preference-item", + Component: () =>
Irrelevant
, + orderNumber: 10, + }), + + injectionToken: preferenceItemInjectionToken, + }); + + runInAction(() => { + windowDi.register(itemForTabInjectable); + }); + }); + + it("renders", () => { + expect(rendered.container).toMatchSnapshot(); + }); + + it("renders the tab group that is no longer empty", () => { + const someTabGroup = rendered.queryByTestId("some-tab-group-test-id"); + + expect(someTabGroup).not.toBeNull(); + }); + + it("renders the tab that is no longer empty", () => { + const someTab = rendered.queryByTestId("some-tab-with-items"); + + expect(someTab).not.toBeNull(); + }); + + it("does not render the tab that is still empty", () => { + const someTab = rendered.queryByTestId("some-tab-without-items"); + + expect(someTab).toBeNull(); + }); + + describe("when an item appears for the remaining tab", () => { + beforeEach(() => { + const itemForTabInjectable = getInjectable({ + id: "some-other-preference-item", + + instantiate: () => ({ + kind: "item" as const, + id: "some-other-preference-item-id", + parentId: "some-tab-without-items-id" as const, + testId: "some-other-preference-item", + Component: () =>
Irrelevant
, + orderNumber: 10, + }), + + injectionToken: preferenceItemInjectionToken, + }); + + runInAction(() => { + windowDi.register(itemForTabInjectable); + }); + }); + + it("renders", () => { + expect(rendered.container).toMatchSnapshot(); + }); + + it("still renders the tab group that is not empty", () => { + const someTabGroup = rendered.queryByTestId("some-tab-group-test-id"); + + expect(someTabGroup).not.toBeNull(); + }); + + it("still renders the tab that is not empty", () => { + const someTab = rendered.queryByTestId("some-tab-with-items"); + + expect(someTab).not.toBeNull(); + }); + + it("now renders the other tab that is no longer empty", () => { + const someTab = rendered.queryByTestId("some-tab-without-items"); + + expect(someTab).not.toBeNull(); + }); + }); + }); + }); +}); diff --git a/src/features/preferences/renderer/preference-items/extensions-preference-tab-group.injectable.ts b/src/features/preferences/renderer/preference-items/extensions-preference-tab-group.injectable.ts index 412ad3a371..4609fc0906 100644 --- a/src/features/preferences/renderer/preference-items/extensions-preference-tab-group.injectable.ts +++ b/src/features/preferences/renderer/preference-items/extensions-preference-tab-group.injectable.ts @@ -11,6 +11,7 @@ const extensionsPreferenceTabGroupInjectable = getInjectable({ instantiate: () => ({ kind: "tab-group" as const, id: "extensions-tab-group", + testId: "extensions-tab-group", parentId: "preference-tabs" as const, label: "Extensions", orderNumber: 20, diff --git a/src/features/preferences/renderer/preference-items/general-preference-tab-group.injectable.ts b/src/features/preferences/renderer/preference-items/general-preference-tab-group.injectable.ts index fe46026aa8..946f34d78a 100644 --- a/src/features/preferences/renderer/preference-items/general-preference-tab-group.injectable.ts +++ b/src/features/preferences/renderer/preference-items/general-preference-tab-group.injectable.ts @@ -11,6 +11,7 @@ const generalPreferenceTabGroupInjectable = getInjectable({ instantiate: () => ({ kind: "tab-group" as const, id: "general-tab-group", + testId: "general-tab-group", parentId: "preference-tabs" as const, label: "Preferences", orderNumber: 10, diff --git a/src/features/preferences/renderer/preference-items/preference-item-injection-token.ts b/src/features/preferences/renderer/preference-items/preference-item-injection-token.ts index 0a2e78a92f..6662a6e645 100644 --- a/src/features/preferences/renderer/preference-items/preference-item-injection-token.ts +++ b/src/features/preferences/renderer/preference-items/preference-item-injection-token.ts @@ -22,6 +22,7 @@ export interface PreferenceTabGroup { kind: "tab-group"; id: string; parentId: "preference-tabs"; + testId: string; label: string; orderNumber: number; isShown?: boolean; diff --git a/src/features/preferences/renderer/preference-navigation/preferences-navigation-tab.tsx b/src/features/preferences/renderer/preference-navigation/preferences-navigation-tab.tsx index 23b908b50d..62a6573874 100644 --- a/src/features/preferences/renderer/preference-navigation/preferences-navigation-tab.tsx +++ b/src/features/preferences/renderer/preference-navigation/preferences-navigation-tab.tsx @@ -23,7 +23,7 @@ interface PreferenceNavigationTabProps { const NonInjectedPreferencesNavigationTab = observer(({ navigateToTab, tabIsActive, tab } : Dependencies & PreferenceNavigationTabProps) => ( navigateToTab(tab.pathId)} - data-testid={`tab-link-for-${tab.pathId}`} + data-testid={tab.testId} active={tabIsActive.get()} label={tab.label} /> diff --git a/src/features/preferences/renderer/preference-navigation/preferences-navigation.tsx b/src/features/preferences/renderer/preference-navigation/preferences-navigation.tsx index 73f2e39596..e1bc94faf8 100644 --- a/src/features/preferences/renderer/preference-navigation/preferences-navigation.tsx +++ b/src/features/preferences/renderer/preference-navigation/preferences-navigation.tsx @@ -12,6 +12,7 @@ import type { IComputedValue } from "mobx"; import preferencesCompositeInjectable from "../preference-items/preferences-composite.injectable"; import { observer } from "mobx-react"; import { PreferencesNavigationTab } from "./preferences-navigation-tab"; +import { compositeHasDescendant } from "../../../application-menu/main/menu-items/get-composite/composite-has-descendant/composite-has-descendant"; interface Dependencies { composite: IComputedValue>; @@ -35,21 +36,34 @@ export const PreferencesNavigation = withInjectables( const toNavigationHierarchy = (composite: Composite) => { + // Note: This makes tab groups and tabs without content not render anything in navigation. + if (!hasContent(composite)) { + return emptyRender; + } + const value = composite.value; switch (value.kind) { - case "page": - case "item": + // Note: These preference item types are not rendered in navigation, + // yet they are interesting for deciding if eg. a tab group or a tab has content + // somewhere in structure, and therefore not be hidden. + case "page": { + return emptyRender; + } + + case "item": { + return emptyRender; + } - // eslint-disable-next-line no-fallthrough case "group": { - throw new Error("Should never come here"); + return emptyRender; } case "tab-group": { + return ( <> -
{value.label}
+
{value.label}
{toNavigationHierarchy} @@ -76,3 +90,10 @@ const toNavigationHierarchy = (composite: Composite) => { } } }; + +const hasContent = compositeHasDescendant( + (composite) => composite.value.kind === "item", +); + +const emptyRender = <>; +