1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Implement registrator for preference items

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Janne Savolainen 2022-10-19 11:02:00 +03:00
parent c581713ca1
commit ad1d9427fe
No known key found for this signature in database
GPG Key ID: 8C6CFB2FFFE8F68A
14 changed files with 436 additions and 301 deletions

View File

@ -7,6 +7,8 @@ import type { IObservableValue } from "mobx";
import { runInAction, computed, observable } from "mobx";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getSingleElement, queryAllElements, querySingleElement } from "../../renderer/components/test-utils/discovery-of-html-elements";
import React from "react";
describe("preferences: extension adding preference tabs", () => {
let builder: ApplicationBuilder;
@ -49,11 +51,43 @@ describe("preferences: extension adding preference tabs", () => {
visible: computed(() => someObservable.get()),
},
],
appPreferences: [
{
title: "some-title",
id: "some-preference-item-id",
showInPreferencesTab: "some-preference-tab-id",
components: {
Hint: () => <div />,
Input: () => <div />,
},
},
{
title: "some-other-title",
id: "some-other-preference-item-id",
showInPreferencesTab: "some-other-preference-tab-id",
components: {
Hint: () => <div />,
Input: () => <div />,
},
},
{
title: "some-another-title",
id: "some-another-preference-item-id",
showInPreferencesTab: "some-preference-tab-id-with-controlled-visibility",
components: {
Hint: () => <div />,
Input: () => <div />,
},
},
],
},
};
builder.extensions.enable(testExtension);
});
it("renders", () => {
@ -61,20 +95,25 @@ describe("preferences: extension adding preference tabs", () => {
});
it("shows tabs in order", () => {
const actual = rendered.queryAllByTestId(/tab-link-for-extension-some-extension-nav-item-(.*)/).map(x => x.dataset.testid);
const actual = queryAllElements("preference-tab-link")(
rendered,
).attributeValues.filter((value) =>
value?.startsWith("extension-some-extension"),
);
expect(actual).toEqual([
"tab-link-for-extension-some-extension-nav-item-some-other-preference-tab-id",
"tab-link-for-extension-some-extension-nav-item-some-preference-tab-id",
"extension-some-extension-some-other-preference-tab-id",
"extension-some-extension-some-preference-tab-id",
]);
});
it("does not show hidden tab", () => {
const actual = rendered.queryByTestId(
"tab-link-for-extension-some-extension-nav-item-some-preference-tab-id-with-controlled-visibility",
);
const actual = querySingleElement(
"preference-tab-link",
"extension-some-extension-some-preference-tab-id-with-controlled-visibility",
)(rendered);
expect(actual).not.toBeInTheDocument();
expect(actual).toBeNull();
});
it("when item becomes visible, shows the tab", () => {
@ -82,11 +121,12 @@ describe("preferences: extension adding preference tabs", () => {
someObservable.set(true);
});
const actual = rendered.queryByTestId(
"tab-link-for-extension-some-extension-nav-item-some-preference-tab-id-with-controlled-visibility",
);
const actual = getSingleElement(
"preference-tab-link",
"extension-some-extension-some-preference-tab-id-with-controlled-visibility",
)(rendered);
expect(actual).toBeInTheDocument();
expect(actual).not.toBeNull();
});
});
});

View File

@ -7,8 +7,9 @@ import type { ApplicationBuilder } from "../../renderer/components/test-utils/ge
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import React from "react";
import "@testing-library/jest-dom/extend-expect";
import extensionPreferencesRouteInjectable from "../../common/front-end-routing/routes/preferences/extension/extension-preferences-route.injectable";
import type { FakeExtensionOptions } from "../../renderer/components/test-utils/get-extension-fake";
import { getSingleElement, queryAllElements, querySingleElement } from "../../renderer/components/test-utils/discovery-of-html-elements";
import logErrorInjectable from "../../common/log-error.injectable";
describe("preferences - navigation to extension specific preferences", () => {
let builder: ApplicationBuilder;
@ -19,9 +20,14 @@ describe("preferences - navigation to extension specific preferences", () => {
describe("given in preferences, when rendered", () => {
let rendered: RenderResult;
let logErrorMock: jest.Mock;
beforeEach(async () => {
builder.beforeWindowStart(() => {
logErrorMock = jest.fn();
builder.beforeWindowStart((windowDi) => {
windowDi.override(logErrorInjectable, () => logErrorMock);
builder.preferences.navigate();
});
@ -33,7 +39,8 @@ describe("preferences - navigation to extension specific preferences", () => {
});
it("does not show extension preferences yet", () => {
const page = rendered.queryByTestId("extension-preferences-page");
// Todo: check if query is correct.
const page = querySingleElement("preference-page", "extension")(rendered);
expect(page).toBeNull();
});
@ -52,7 +59,7 @@ describe("preferences - navigation to extension specific preferences", () => {
);
builder.preferences.navigation.click(
"extension-some-test-extension-id",
"some-test-extension-id",
);
});
@ -61,13 +68,19 @@ describe("preferences - navigation to extension specific preferences", () => {
});
it("doesn't show preferences from unrelated extension", () => {
const actual = rendered.queryByTestId("extension-preference-item-for-some-other-preference-item-id");
const actual = querySingleElement(
"preference-page",
"preference-item-for-extension-some-other-test-extension-id-page",
)(rendered);
expect(actual).toBeNull();
});
it("shows preferences from related extension", () => {
const actual = rendered.getByTestId("extension-preference-item-for-some-preference-item-id");
const actual = getSingleElement(
"preference-page",
"preference-item-for-extension-some-test-extension-id-page",
)(rendered);
expect(actual).not.toBeNull();
});
@ -104,21 +117,18 @@ describe("preferences - navigation to extension specific preferences", () => {
expect(rendered.container).toMatchSnapshot();
});
it("shows link for extension preferences", () => {
const actual = rendered.getByTestId("tab-link-for-extension-some-test-extension-id");
expect(actual).not.toBeNull();
});
it("link should not be active", () => {
const actual = rendered.getByTestId("tab-link-for-extension-some-test-extension-id");
const actual = getSingleElement(
"preference-tab-link",
"some-test-extension-id",
)(rendered);
expect(actual).not.toHaveClass("active");
});
describe("when navigating to extension preferences using navigation", () => {
beforeEach(() => {
builder.preferences.navigation.click("extension-some-test-extension-id");
builder.preferences.navigation.click("some-test-extension-id");
});
it("renders", () => {
@ -126,31 +136,23 @@ describe("preferences - navigation to extension specific preferences", () => {
});
it("shows proper page title", () => {
const title = rendered.getByText("some-test-extension-id preferences");
const title = getSingleElement("preference-page-title")(rendered);
expect(title).toBeInTheDocument();
expect(title).toHaveTextContent("some-test-extension-id preferences");
});
it("shows extension specific preferences", () => {
const page = rendered.getByTestId("extension-preferences-page");
it("shows only extension specific preference items", () => {
const actual = queryAllElements(
"preference-item",
)(rendered).attributeValues;
expect(page).not.toBeNull();
});
it("shows extension specific preference item", () => {
const actual = rendered.getByTestId("extension-preference-item-for-some-preference-item-id");
expect(actual).not.toBeNull();
});
it("does not show unrelated preference tab items", () => {
const actual = rendered.queryByTestId("extension-preference-item-for-some-unrelated-preference-item-id");
expect(actual).toBeNull();
expect(actual).toEqual([
"preference-item-for-extension-some-test-extension-id-item-some-preference-item-id",
]);
});
it("link is active", () => {
const actual = rendered.getByTestId("tab-link-for-extension-some-test-extension-id");
const actual = getSingleElement("preference-tab-link", "some-test-extension-id")(rendered);
expect(actual).toHaveClass("active");
});
@ -164,14 +166,21 @@ describe("preferences - navigation to extension specific preferences", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
it("shows the error message about extension not being present", () => {
expect(rendered.getByTestId("error-for-extension-not-being-present")).toBeInTheDocument();
it("does not show any preference page", () => {
const actual = querySingleElement("preference-page")(rendered);
expect(actual).toBeNull();
});
it("when extension is enabled again, does not show the error message anymore", () => {
it("when extension is enabled again, shows the preference page", () => {
builder.extensions.enable(extensionStubWithExtensionSpecificPreferenceItems);
expect(rendered.queryByTestId("error-for-extension-not-being-present")).not.toBeInTheDocument();
const actual = getSingleElement(
"preference-page",
"preference-item-for-extension-some-test-extension-id-page",
)(rendered);
expect(actual).not.toBeNull();
});
});
});
@ -179,90 +188,104 @@ describe("preferences - navigation to extension specific preferences", () => {
describe("given extension with registered tab", () => {
beforeEach(() => {
builder.extensions.enable(extensionStubWithWithRegisteredTab);
builder.extensions.enable(extensionStubWithRegisteredTab);
});
it("logs error", () => {
expect(
logErrorMock.mock.calls[0][0].startsWith(
"Tried to create preferences, but encountered references to unknown ids",
),
).toBe(true);
});
it("renders", () => {
expect(rendered.baseElement).toMatchSnapshot();
});
it("shows extension tab in general area", () => {
const actual = rendered.getByTestId("tab-link-for-extension-registered-tab-page-id-nav-item-metrics-extension-tab");
const generalTabGroup = getSingleElement(
"preference-tab-group",
"general-tab-group",
)(rendered);
expect(actual).toMatchSnapshot();
const actual = getSingleElement(
"preference-tab-link",
"extension-registered-tab-page-id-metrics-extension-tab",
)(generalTabGroup);
expect(actual).not.toBeNull();
});
it("does not show custom settings block", () => {
const actual = rendered.queryByTestId("extension-settings");
it("does not show tab group for extensions for there being no content", () => {
const actual = querySingleElement(
"preference-tab-group",
"extensions-tab-group",
)(rendered);
expect(actual).not.toBeInTheDocument();
expect(actual).toBeNull();
});
describe("when navigating to specific extension tab", () => {
beforeEach(() => {
builder.preferences.navigation.click("extension-registered-tab-page-id-nav-item-metrics-extension-tab");
builder.preferences.navigation.click("extension-registered-tab-page-id-metrics-extension-tab");
});
it("renders", () => {
expect(rendered.container).toMatchSnapshot();
});
it("shows related preferences for this tab", () => {
const actual = rendered.getByTestId("metrics-preference-item-hint");
expect(actual).toBeInTheDocument();
});
it("does not show unrelated preferences for this tab", () => {
const actual = rendered.queryByTestId("survey-preference-item-hint");
expect(actual).not.toBeInTheDocument();
});
it("shows page title", () => {
const pageTitle = rendered.queryByTestId("extension-preferences-page-title");
expect(pageTitle).toBeInTheDocument();
it("shows correct page title", () => {
const pageTitle = getSingleElement("preference-page-title")(rendered);
expect(pageTitle).toHaveTextContent("Metrics tab");
});
it("shows page title same as tab title", () => {
const pageTitle = rendered.queryByTestId("extension-preferences-page-title");
const tabs = extensionStubWithWithRegisteredTab.rendererOptions?.appPreferenceTabs;
const tabTitle = tabs && tabs[0].title;
expect(pageTitle?.innerHTML).toBe(tabTitle);
});
});
});
describe("given extension with few registered tabs", () => {
const tabs = [
"tab-link-for-extension-hello-world-tab-page-id-nav-item-hello-extension-tab",
"tab-link-for-extension-hello-world-tab-page-id-nav-item-logs-extension-tab",
];
beforeEach(() => {
builder.extensions.enable(extensionStubWithWithRegisteredTabs);
});
it.each(tabs)("shows '%s' tab in general area", (tab) => {
const tabElement = rendered.getByTestId(tab);
expect(tabElement).toBeInTheDocument();
});
});
describe("given extensions with tabs having same id", () => {
beforeEach(() => {
builder.extensions.enable(extensionStubWithWithRegisteredTab, extensionStubWithWithSameRegisteredTab);
builder.extensions.enable(
extensionStubWithRegisteredTab,
extensionStubWithSameRegisteredTab,
);
});
it("shows tab from the first extension", () => {
const actual = rendered.getByTestId("tab-link-for-extension-registered-tab-page-id-nav-item-metrics-extension-tab");
const actual = getSingleElement(
"preference-tab-link",
"extension-registered-tab-page-id-metrics-extension-tab",
)(rendered);
expect(actual).toBeInTheDocument();
});
it("shows tab from the second extension", () => {
const actual = rendered.getByTestId("tab-link-for-extension-duplicated-tab-page-id-nav-item-metrics-extension-tab");
const actual = getSingleElement(
"preference-tab-link",
"extension-duplicated-tab-page-id-metrics-extension-tab",
)(rendered);
expect(actual).toBeInTheDocument();
});
describe("when navigating to first extension tab", () => {
beforeEach(() => {
builder.preferences.navigation.click("extension-registered-tab-page-id-nav-item-metrics-extension-tab");
builder.preferences.navigation.click(
"extension-registered-tab-page-id-metrics-extension-tab",
);
});
it("renders", () => {
@ -270,21 +293,20 @@ describe("preferences - navigation to extension specific preferences", () => {
});
it("shows related preferences for this tab", () => {
const actual = rendered.getByTestId("metrics-preference-item-hint");
const actual =
queryAllElements("preference-item")(rendered).attributeValues;
expect(actual).toBeInTheDocument();
});
it("does not show unrelated preferences for this tab", () => {
const actual = rendered.queryByTestId("another-metrics-preference-item-hint");
expect(actual).not.toBeInTheDocument();
expect(actual).toEqual([
"preference-item-for-extension-registered-tab-page-id-item-metrics-preference-item-id",
]);
});
});
describe("when navigating to second extension tab", () => {
beforeEach(() => {
builder.preferences.navigation.click("extension-duplicated-tab-page-id-nav-item-metrics-extension-tab");
builder.preferences.navigation.click(
"extension-duplicated-tab-page-id-metrics-extension-tab",
);
});
it("renders", () => {
@ -292,74 +314,16 @@ describe("preferences - navigation to extension specific preferences", () => {
});
it("shows related preferences for this tab", () => {
const actual = rendered.getByTestId("another-metrics-preference-item-hint");
const actual =
queryAllElements("preference-item")(rendered).attributeValues;
expect(actual).toBeInTheDocument();
});
it("does not show unrelated preferences for this tab", () => {
const actual = rendered.queryByTestId("metrics-preference-item-hint");
expect(actual).not.toBeInTheDocument();
expect(actual).toEqual([
"preference-item-for-extension-duplicated-tab-page-id-item-another-metrics-preference-item-id",
]);
});
});
});
});
describe("when navigating to extension specific tab", () => {
let rendered: RenderResult;
beforeEach(async () => {
builder.beforeWindowStart((windowDi) => {
const extensionRoute = windowDi.inject(extensionPreferencesRouteInjectable);
const params = { parameters: {
extensionId: "duplicated-tab-page-id",
tabId: "metrics-extension-tab",
}};
builder.preferences.navigateTo(extensionRoute, params);
});
builder.extensions.enable(extensionStubWithWithSameRegisteredTab, extensionUsingSomeoneElseTab);
rendered = await builder.render();
});
it("renders", () => {
expect(rendered.container).toMatchSnapshot();
});
it("does render related preferences for specific tab", () => {
expect(rendered.getByTestId("another-metrics-preference-item-hint")).toBeInTheDocument();
});
it("does not render related preferences for specific tab", () => {
expect(rendered.queryByTestId("my-preferences-item-hint")).not.toBeInTheDocument();
});
});
describe("when navigating to someone else extension specific tab", () => {
let rendered: RenderResult;
beforeEach(async () => {
builder.beforeWindowStart((windowDi) => {
const extensionRoute = windowDi.inject(extensionPreferencesRouteInjectable);
const params = { parameters: {
extensionId: "extension-using-someone-else-tab-id",
tabId: "metrics-extension-tab",
}};
builder.preferences.navigateTo(extensionRoute, params);
});
builder.extensions.enable(extensionStubWithWithSameRegisteredTab, extensionUsingSomeoneElseTab);
rendered = await builder.render();
});
it("renders", () => {
expect(rendered.container).toMatchSnapshot();
});
});
});
const extensionStubWithExtensionSpecificPreferenceItems: FakeExtensionOptions = {
@ -436,7 +400,7 @@ const extensionStubWithShowInPreferencesTab: FakeExtensionOptions = {
},
};
const extensionStubWithWithRegisteredTab: FakeExtensionOptions = {
const extensionStubWithRegisteredTab: FakeExtensionOptions = {
id: "registered-tab-page-id",
name: "registered-tab-page-id",
@ -482,47 +446,7 @@ const extensionStubWithWithRegisteredTab: FakeExtensionOptions = {
},
};
const extensionStubWithWithRegisteredTabs: FakeExtensionOptions = {
id: "hello-world-tab-page-id",
name: "hello-world-tab-page-id",
rendererOptions: {
appPreferences: [
{
title: "Hello world",
id: "hello-preference-item-id",
showInPreferencesTab: "hello-extension-tab",
components: {
Hint: () => <div data-testid="hello-preference-item-hint" />,
Input: () => <div data-testid="hello-preference-item-input" />,
},
},
{
title: "Logs",
id: "logs-preference-item-id",
showInPreferencesTab: "logs-extension-tab",
components: {
Hint: () => <div data-testid="logs-preference-item-hint" />,
Input: () => <div data-testid="logs-preference-item-input" />,
},
},
],
appPreferenceTabs: [{
title: "Metrics tab",
id: "hello-extension-tab",
orderNumber: 100,
}, {
title: "Logs tab",
id: "logs-extension-tab",
orderNumber: 200,
}],
},
};
const extensionStubWithWithSameRegisteredTab: FakeExtensionOptions = {
const extensionStubWithSameRegisteredTab: FakeExtensionOptions = {
id: "duplicated-tab-page-id",
name: "duplicated-tab-page-id",
@ -547,23 +471,3 @@ const extensionStubWithWithSameRegisteredTab: FakeExtensionOptions = {
}],
},
};
const extensionUsingSomeoneElseTab: FakeExtensionOptions = {
id: "extension-using-someone-else-tab-id",
name: "extension-using-someone-else-tab-id",
rendererOptions: {
appPreferences: [
{
title: "My preferences",
id: "my-preferences-item-id",
showInPreferencesTab: "metrics-extension-tab",
components: {
Hint: () => <div data-testid="my-preferences-item-hint" />,
Input: () => <div data-testid="my-preferences-item-input" />,
},
},
],
},
};

View File

@ -9,7 +9,7 @@ import { getApplicationBuilder } from "../../renderer/components/test-utils/get-
import navigateToTelemetryPreferencesInjectable from "./common/navigate-to-telemetry-preferences.injectable";
import sentryDataSourceNameInjectable from "../../common/vars/sentry-dsn-url.injectable";
import type { FakeExtensionOptions } from "../../renderer/components/test-utils/get-extension-fake";
import { getSingleElement, querySingleElement } from "../../renderer/components/test-utils/discovery-of-html-elements";
import { getSingleElement, queryAllElements, querySingleElement } from "../../renderer/components/test-utils/discovery-of-html-elements";
describe("preferences - navigation to telemetry preferences", () => {
let builder: ApplicationBuilder;
@ -43,7 +43,10 @@ describe("preferences - navigation to telemetry preferences", () => {
});
it("does not show link for telemetry preferences", () => {
const actual = rendered.queryByTestId("tab-link-for-telemetry");
const actual = querySingleElement(
"preference-tab-link",
"telemetry",
)(rendered);
expect(actual).toBeNull();
});
@ -60,7 +63,10 @@ describe("preferences - navigation to telemetry preferences", () => {
});
it("shows link for telemetry preferences", () => {
const actual = rendered.getByTestId("tab-link-for-telemetry");
const actual = getSingleElement(
"preference-tab-link",
"telemetry",
)(rendered);
expect(actual).not.toBeNull();
});
@ -84,11 +90,12 @@ describe("preferences - navigation to telemetry preferences", () => {
});
it("shows extension telemetry preference items", () => {
const actual = rendered.getByTestId(
"telemetry-preference-item-for-some-telemetry-preference-item-id",
);
const actual =
queryAllElements("preference-item")(rendered).attributeValues;
expect(actual).not.toBeNull();
expect(actual).toEqual([
"preference-item-for-extension-some-test-extension-name-item-some-telemetry-preference-item-id",
]);
});
});
});
@ -110,7 +117,10 @@ describe("preferences - navigation to telemetry preferences", () => {
},
});
const actual = rendered.queryByTestId("tab-link-for-telemetry");
const actual = querySingleElement(
"preference-tab-link",
"telemetry",
)(rendered);
expect(actual).toBeNull();
});
@ -139,9 +149,11 @@ describe("preferences - navigation to telemetry preferences", () => {
});
it("allows configuration of automatic error reporting", () => {
const actual = rendered.getByTestId("telemetry-preferences-for-automatic-error-reporting");
const actual = queryAllElements(
"preference-item",
)(rendered).attributeValues;
expect(actual).not.toBeNull();
expect(actual).toEqual(["automatic-error-reporting"]);
});
});
});
@ -168,9 +180,11 @@ describe("preferences - navigation to telemetry preferences", () => {
});
it("does not allow configuration of automatic error reporting", () => {
const actual = rendered.queryByTestId("telemetry-preferences-for-automatic-error-reporting");
const actual = queryAllElements(
"preference-item",
)(rendered).attributeValues;
expect(actual).toBeNull();
expect(actual).toEqual([]);
});
});
});

View File

@ -0,0 +1,35 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { SubTitle } from "../../../../renderer/components/layout/sub-title";
import type { AppPreferenceRegistration } from "../../../../renderer/components/+preferences/app-preferences/app-preference-registration";
import React from "react";
export interface ExtensionSettingsProps {
registration: AppPreferenceRegistration;
}
export function ExtensionPreferenceItem({ registration }: ExtensionSettingsProps) {
const {
title,
id,
components: { Hint, Input },
} = registration;
return (
<React.Fragment>
<section
id={id}
className="small"
>
<SubTitle title={title} />
<Input />
<div className="hint">
<Hint />
</div>
</section>
<hr className="small" />
</React.Fragment>
);
}

View File

@ -0,0 +1,138 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { getInjectable } from "@ogre-tools/injectable";
import type { LensRendererExtension } from "../../../../extensions/lens-renderer-extension";
import { preferenceItemInjectionToken } from "../preference-items/preference-item-injection-token";
import { extensionRegistratorInjectionToken } from "../../../../extensions/extension-loader/extension-registrator-injection-token";
import { getPreferencePage } from "../get-preference-page";
import { ExtensionPreferenceItem } from "./extension-preference-item";
import { computed } from "mobx";
const registratorForPreferenceItemsInjectable = getInjectable({
id: "registrator-for-preference-items",
instantiate: () => (ext) => {
const extension = ext as LensRendererExtension;
const commonId = `preference-item-for-extension-${extension.sanitizedExtensionId}`;
const tabId = `${commonId}-primary-tab`;
const primaryTabInjectable = getInjectable({
id: tabId,
instantiate: () => ({
kind: "tab" as const,
id: tabId,
parentId: "extensions-tab-group",
pathId: extension.sanitizedExtensionId,
label: extension.name,
orderNumber: 10,
}),
injectionToken: preferenceItemInjectionToken,
});
const pageId = `${commonId}-page`;
const primaryPageInjectable = getInjectable({
id: pageId,
instantiate: () => ({
kind: "page" as const,
id: pageId,
parentId: tabId,
orderNumber: 0,
Component: getPreferencePage(`${extension.name} preferences`),
childrenSeparator: () => <hr />,
}),
injectionToken: preferenceItemInjectionToken,
});
const additionalTabs = extension.appPreferenceTabs.map(
(registration) => {
const additionalTabId = `${commonId}-additional-tab-${registration.id}`;
return getInjectable({
id: additionalTabId,
instantiate: () => ({
kind: "tab" as const,
id: additionalTabId,
parentId: "general-tab-group",
pathId: `extension-${extension.sanitizedExtensionId}-${registration.id}`,
label: registration.title,
isShown: computed(() => registration.visible?.get() ?? true),
orderNumber: registration.orderNumber || 100,
}),
injectionToken: preferenceItemInjectionToken,
});
},
);
const additionalPages = extension.appPreferenceTabs.map(
(registration) => {
const additionalPageId = `${commonId}-additional-page-${registration.id}`;
const additionalTabId = `${commonId}-additional-tab-${registration.id}`;
return getInjectable({
id: additionalPageId,
instantiate: () => ({
kind: "page" as const,
id: additionalPageId,
parentId: additionalTabId,
Component: getPreferencePage(registration.title),
}),
injectionToken: preferenceItemInjectionToken,
});
},
);
const items = extension.appPreferences.map((registration, i) => {
const itemId = `${commonId}-item-${registration.id ?? i}`;
return getInjectable({
id: itemId,
instantiate: () => ({
kind: "item" as const,
id: itemId,
// Note: Legacy extensions considered telemetry as magic string, and so does this code
parentId: registration.showInPreferencesTab
? registration.showInPreferencesTab === "telemetry"
? "telemetry-page"
: `${commonId}-additional-page-${registration.showInPreferencesTab}`
: pageId,
orderNumber: i * 10,
Component: () => (
<ExtensionPreferenceItem registration={registration} />
),
childrenSeparator: () => <hr />,
}),
injectionToken: preferenceItemInjectionToken,
});
});
return [
primaryTabInjectable,
...additionalTabs,
primaryPageInjectable,
...additionalPages,
...items,
];
},
injectionToken: extensionRegistratorInjectionToken,
});
export default registratorForPreferenceItemsInjectable;

View File

@ -7,7 +7,7 @@ import type { PreferencePageComponent } from "./preference-items/preference-item
export const getPreferencePage = (label: string): PreferencePageComponent => ({ children, item }) => (
<section id={item.id} data-preference-page-test={item.id}>
<h2>{label}</h2>
<h2 data-preference-page-title-test={true}>{label}</h2>
{children}
</section>

View File

@ -11,7 +11,6 @@ import preferencesRouteInjectable from "../../common/preferences-route.injectabl
import { filter, map } from "lodash/fp";
import { pipeline } from "@ogre-tools/fp";
import { normalizeComposite } from "../../../application-menu/main/menu-items/get-composite/normalize-composite/normalize-composite";
import { findExactlyOne } from "../../../../common/utils/find-exactly-one/find-exactly-one";
import preferencesCompositeInjectable from "./preferences-composite.injectable";
import type { PreferenceTabsRoot } from "./preference-tab-root";
@ -26,12 +25,18 @@ const currentPreferenceTabCompositeInjectable = getInjectable({
return computed(() => {
const { preferenceTabId } = routePathParameters.get();
return pipeline(
const tabComposites = pipeline(
normalizeComposite(preferencesComposite.get()),
map(([, composite]) => composite),
filter(isPreferenceTab),
findExactlyOne(hasMatchingPathId(preferenceTabId)),
filter(hasMatchingPathId(preferenceTabId)),
);
if (tabComposites.length === 0) {
return undefined;
}
return tabComposites[0];
});
},
});

View File

@ -3,6 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectionToken } from "@ogre-tools/injectable";
import type { IComputedValue } from "mobx";
import type React from "react";
export type PreferenceItemComponent = React.ComponentType<{
@ -21,7 +22,7 @@ export interface PreferenceTab {
pathId: string;
label: string;
orderNumber: number;
isShown?: boolean;
isShown?: IComputedValue<boolean> | boolean;
}
export interface PreferenceTabGroup {
@ -30,7 +31,7 @@ export interface PreferenceTabGroup {
parentId: "preference-tabs";
label: string;
orderNumber: number;
isShown?: boolean;
isShown?: IComputedValue<boolean> | boolean;
iconName?: string;
}
@ -38,7 +39,7 @@ export interface PreferencePage {
kind: "page";
id: string;
parentId: string;
isShown?: boolean;
isShown?: IComputedValue<boolean> | boolean;
childrenSeparator?: () => React.ReactElement;
Component: PreferencePageComponent;
}
@ -47,7 +48,7 @@ export interface PreferenceGroup {
kind: "group";
id: string;
parentId: string;
isShown?: boolean;
isShown?: IComputedValue<boolean> | boolean;
childrenSeparator?: () => React.ReactElement;
}
@ -57,7 +58,7 @@ export interface PreferenceItem {
id: string;
parentId: string;
orderNumber: number;
isShown?: boolean;
isShown?: IComputedValue<boolean> | boolean;
childrenSeparator?: () => React.ReactElement;
}

View File

@ -2,13 +2,15 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { IComputedValue } from "mobx";
import { computed } from "mobx";
import React from "react";
export interface PreferenceTabsRoot {
kind: "preference-tabs-root";
id: string;
parentId: undefined;
isShown: true;
isShown: IComputedValue<true>;
childrenSeparator: () => React.ReactElement;
}
@ -16,6 +18,6 @@ export const preferenceTabsRoot: PreferenceTabsRoot = {
kind: "preference-tabs-root" as const,
id: "preference-tabs",
parentId: undefined,
isShown: true,
isShown: computed(() => true as const),
childrenSeparator: () => <hr />,
};

View File

@ -11,6 +11,8 @@ import getComposite from "../../../application-menu/main/menu-items/get-composit
import { filter } from "lodash/fp";
import { pipeline } from "@ogre-tools/fp";
import { preferenceTabsRoot } from "./preference-tab-root";
import logErrorInjectable from "../../../../common/log-error.injectable";
import { isBoolean } from "../../../../common/utils";
const preferencesCompositeInjectable = getInjectable({
id: "preferences-composite",
@ -18,17 +20,41 @@ const preferencesCompositeInjectable = getInjectable({
instantiate: (di) => {
const computedInjectMany = di.inject(computedInjectManyInjectable);
const preferenceItems = computedInjectMany(preferenceItemInjectionToken);
const logError = di.inject(logErrorInjectable);
return computed(() =>
pipeline(
[preferenceTabsRoot, ...preferenceItems.get()],
filter(isShown),
(items) => getComposite({ source: items }),
filter((item: PreferenceTypes) => {
if (item.isShown === undefined) {
return true;
}
if (isBoolean(item.isShown)) {
return item.isShown;
}
return item.isShown.get();
}),
(items) =>
getComposite({
source: items,
handleMissingParentIds: (ids) => {
logError(
`Tried to create preferences, but encountered references to unknown ids: "${ids.missingParentIds.join(
'", "',
)}". Available ids are: "${ids.availableParentIds.join(
'", "',
)}"`,
);
},
}),
),
);
},
});
const isShown = (item: PreferenceTypes) => item.isShown ?? true;
export default preferencesCompositeInjectable;

View File

@ -63,8 +63,8 @@ const toNavigationHierarchy = (composite: Composite<PreferenceTypes | Preference
case "tab-group": {
return (
<>
<div className="header flex items-center" data-preference-tab-group-test={value.id}>
<div data-preference-tab-group-test={value.id}>
<div className="header flex items-center">
{value.iconName && (
<Icon
material={value.iconName}
@ -76,7 +76,7 @@ const toNavigationHierarchy = (composite: Composite<PreferenceTypes | Preference
</div>
<Map items={composite.children}>{toNavigationHierarchy}</Map>
</>
</div>
);
}

View File

@ -18,7 +18,7 @@ import { PreferencesNavigation } from "./preference-navigation/preferences-navig
interface Dependencies {
closePreferences: () => void;
pageComposite: IComputedValue<Composite<PreferenceTab>>;
pageComposite: IComputedValue<Composite<PreferenceTab> | undefined>;
}
const NonInjectedPreferences = observer(({
@ -35,7 +35,16 @@ const NonInjectedPreferences = observer(({
closeButtonProps={{ "data-testid": "close-preferences" }}
back={closePreferences}
>
{toPreferenceItemHierarchy(composite)}
{composite ? (
toPreferenceItemHierarchy(composite)
) : (
<div
className="flex items-center"
data-preference-page-does-not-exist-test={true}
>
No preferences found
</div>
)}
</SettingLayout>
);
});
@ -58,13 +67,14 @@ const toPreferenceItemHierarchy = (composite: Composite<PreferenceTypes>) => {
const Component = value.Component;
return (
<Component>
<Map items={composite.children} getSeparator={value.childrenSeparator}>
{toPreferenceItemHierarchy}
</Map>
</Component>
<div data-preference-item-test={composite.id}>
<Component>
<Map items={composite.children} getSeparator={value.childrenSeparator}>
{toPreferenceItemHierarchy}
</Map>
</Component>
</div>
);
}
// eslint-disable-next-line no-fallthrough

View File

@ -1,39 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { SubTitle } from "../layout/sub-title";
import type { AppPreferenceRegistration } from "./app-preferences/app-preference-registration";
import type { DOMAttributes } from "react";
import React from "react";
import { cssNames } from "../../../renderer/utils";
export interface ExtensionSettingsProps extends DOMAttributes<HTMLDivElement> {
setting: AppPreferenceRegistration;
size: "small" | "normal";
}
export function ExtensionSettings({ setting, size, ...props }: ExtensionSettingsProps) {
const {
title,
id,
components: { Hint, Input },
} = setting;
return (
<React.Fragment>
<section
id={id}
className={cssNames(size)}
{...props}
>
<SubTitle title={title} />
<Input />
<div className="hint">
<Hint />
</div>
</section>
<hr className={cssNames(size)} />
</React.Fragment>
);
}

View File

@ -9,7 +9,7 @@ import { observer } from "mobx-react";
import React from "react";
import type { RegisteredAppPreference } from "./app-preferences/app-preference-registration";
import extensionPreferencesModelInjectable from "./extension-preference-model.injectable";
import { ExtensionSettings } from "./extension-settings";
import { ExtensionPreferenceItem } from "../../../features/preferences/renderer/compliance-for-legacy-extension-api/extension-preference-item";
interface Dependencies {
model: IComputedValue<{
@ -36,10 +36,9 @@ const NonInjectedExtensions = ({ model }: Dependencies) => {
</div>
)}
{preferenceItems.map((preferenceItem, index) => (
<ExtensionSettings
<ExtensionPreferenceItem
key={`${preferenceItem.id}-${index}`}
setting={preferenceItem}
size="small"
registration={preferenceItem}
data-testid={`extension-preference-item-for-${preferenceItem.id}`}
/>
))}