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 { runInAction, computed, observable } from "mobx";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } 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", () => { describe("preferences: extension adding preference tabs", () => {
let builder: ApplicationBuilder; let builder: ApplicationBuilder;
@ -49,11 +51,43 @@ describe("preferences: extension adding preference tabs", () => {
visible: computed(() => someObservable.get()), 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); builder.extensions.enable(testExtension);
}); });
it("renders", () => { it("renders", () => {
@ -61,20 +95,25 @@ describe("preferences: extension adding preference tabs", () => {
}); });
it("shows tabs in order", () => { 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([ expect(actual).toEqual([
"tab-link-for-extension-some-extension-nav-item-some-other-preference-tab-id", "extension-some-extension-some-other-preference-tab-id",
"tab-link-for-extension-some-extension-nav-item-some-preference-tab-id", "extension-some-extension-some-preference-tab-id",
]); ]);
}); });
it("does not show hidden tab", () => { it("does not show hidden tab", () => {
const actual = rendered.queryByTestId( const actual = querySingleElement(
"tab-link-for-extension-some-extension-nav-item-some-preference-tab-id-with-controlled-visibility", "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", () => { it("when item becomes visible, shows the tab", () => {
@ -82,11 +121,12 @@ describe("preferences: extension adding preference tabs", () => {
someObservable.set(true); someObservable.set(true);
}); });
const actual = rendered.queryByTestId( const actual = getSingleElement(
"tab-link-for-extension-some-extension-nav-item-some-preference-tab-id-with-controlled-visibility", "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 { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import React from "react"; import React from "react";
import "@testing-library/jest-dom/extend-expect"; 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 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", () => { describe("preferences - navigation to extension specific preferences", () => {
let builder: ApplicationBuilder; let builder: ApplicationBuilder;
@ -19,9 +20,14 @@ describe("preferences - navigation to extension specific preferences", () => {
describe("given in preferences, when rendered", () => { describe("given in preferences, when rendered", () => {
let rendered: RenderResult; let rendered: RenderResult;
let logErrorMock: jest.Mock;
beforeEach(async () => { beforeEach(async () => {
builder.beforeWindowStart(() => { logErrorMock = jest.fn();
builder.beforeWindowStart((windowDi) => {
windowDi.override(logErrorInjectable, () => logErrorMock);
builder.preferences.navigate(); builder.preferences.navigate();
}); });
@ -33,7 +39,8 @@ describe("preferences - navigation to extension specific preferences", () => {
}); });
it("does not show extension preferences yet", () => { 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(); expect(page).toBeNull();
}); });
@ -52,7 +59,7 @@ describe("preferences - navigation to extension specific preferences", () => {
); );
builder.preferences.navigation.click( 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", () => { 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(); expect(actual).toBeNull();
}); });
it("shows preferences from related extension", () => { 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(); expect(actual).not.toBeNull();
}); });
@ -104,21 +117,18 @@ describe("preferences - navigation to extension specific preferences", () => {
expect(rendered.container).toMatchSnapshot(); 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", () => { 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"); expect(actual).not.toHaveClass("active");
}); });
describe("when navigating to extension preferences using navigation", () => { describe("when navigating to extension preferences using navigation", () => {
beforeEach(() => { beforeEach(() => {
builder.preferences.navigation.click("extension-some-test-extension-id"); builder.preferences.navigation.click("some-test-extension-id");
}); });
it("renders", () => { it("renders", () => {
@ -126,31 +136,23 @@ describe("preferences - navigation to extension specific preferences", () => {
}); });
it("shows proper page title", () => { 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", () => { it("shows only extension specific preference items", () => {
const page = rendered.getByTestId("extension-preferences-page"); const actual = queryAllElements(
"preference-item",
)(rendered).attributeValues;
expect(page).not.toBeNull(); expect(actual).toEqual([
}); "preference-item-for-extension-some-test-extension-id-item-some-preference-item-id",
]);
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();
}); });
it("link is active", () => { 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"); expect(actual).toHaveClass("active");
}); });
@ -164,14 +166,21 @@ describe("preferences - navigation to extension specific preferences", () => {
expect(rendered.baseElement).toMatchSnapshot(); expect(rendered.baseElement).toMatchSnapshot();
}); });
it("shows the error message about extension not being present", () => { it("does not show any preference page", () => {
expect(rendered.getByTestId("error-for-extension-not-being-present")).toBeInTheDocument(); 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); 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", () => { describe("given extension with registered tab", () => {
beforeEach(() => { 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", () => { 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", () => { it("does not show tab group for extensions for there being no content", () => {
const actual = rendered.queryByTestId("extension-settings"); const actual = querySingleElement(
"preference-tab-group",
"extensions-tab-group",
)(rendered);
expect(actual).not.toBeInTheDocument(); expect(actual).toBeNull();
}); });
describe("when navigating to specific extension tab", () => { describe("when navigating to specific extension tab", () => {
beforeEach(() => { 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", () => { it("renders", () => {
expect(rendered.container).toMatchSnapshot(); expect(rendered.container).toMatchSnapshot();
}); });
it("shows related preferences for this tab", () => { it("shows related preferences for this tab", () => {
const actual = rendered.getByTestId("metrics-preference-item-hint"); const actual = rendered.getByTestId("metrics-preference-item-hint");
expect(actual).toBeInTheDocument(); expect(actual).toBeInTheDocument();
}); });
it("does not show unrelated preferences for this tab", () => { it("does not show unrelated preferences for this tab", () => {
const actual = rendered.queryByTestId("survey-preference-item-hint"); const actual = rendered.queryByTestId("survey-preference-item-hint");
expect(actual).not.toBeInTheDocument(); 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", () => { describe("given extensions with tabs having same id", () => {
beforeEach(() => { beforeEach(() => {
builder.extensions.enable(extensionStubWithWithRegisteredTab, extensionStubWithWithSameRegisteredTab); builder.extensions.enable(
extensionStubWithRegisteredTab,
extensionStubWithSameRegisteredTab,
);
}); });
it("shows tab from the first extension", () => { 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(); expect(actual).toBeInTheDocument();
}); });
it("shows tab from the second extension", () => { 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(); expect(actual).toBeInTheDocument();
}); });
describe("when navigating to first extension tab", () => { describe("when navigating to first extension tab", () => {
beforeEach(() => { 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", () => { it("renders", () => {
@ -270,21 +293,20 @@ describe("preferences - navigation to extension specific preferences", () => {
}); });
it("shows related preferences for this tab", () => { 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(); expect(actual).toEqual([
}); "preference-item-for-extension-registered-tab-page-id-item-metrics-preference-item-id",
]);
it("does not show unrelated preferences for this tab", () => {
const actual = rendered.queryByTestId("another-metrics-preference-item-hint");
expect(actual).not.toBeInTheDocument();
}); });
}); });
describe("when navigating to second extension tab", () => { describe("when navigating to second extension tab", () => {
beforeEach(() => { 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", () => { it("renders", () => {
@ -292,74 +314,16 @@ describe("preferences - navigation to extension specific preferences", () => {
}); });
it("shows related preferences for this tab", () => { 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(); expect(actual).toEqual([
}); "preference-item-for-extension-duplicated-tab-page-id-item-another-metrics-preference-item-id",
]);
it("does not show unrelated preferences for this tab", () => {
const actual = rendered.queryByTestId("metrics-preference-item-hint");
expect(actual).not.toBeInTheDocument();
}); });
}); });
}); });
}); });
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 = { const extensionStubWithExtensionSpecificPreferenceItems: FakeExtensionOptions = {
@ -436,7 +400,7 @@ const extensionStubWithShowInPreferencesTab: FakeExtensionOptions = {
}, },
}; };
const extensionStubWithWithRegisteredTab: FakeExtensionOptions = { const extensionStubWithRegisteredTab: FakeExtensionOptions = {
id: "registered-tab-page-id", id: "registered-tab-page-id",
name: "registered-tab-page-id", name: "registered-tab-page-id",
@ -482,47 +446,7 @@ const extensionStubWithWithRegisteredTab: FakeExtensionOptions = {
}, },
}; };
const extensionStubWithWithRegisteredTabs: FakeExtensionOptions = { const extensionStubWithSameRegisteredTab: 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 = {
id: "duplicated-tab-page-id", id: "duplicated-tab-page-id",
name: "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 navigateToTelemetryPreferencesInjectable from "./common/navigate-to-telemetry-preferences.injectable";
import sentryDataSourceNameInjectable from "../../common/vars/sentry-dsn-url.injectable"; import sentryDataSourceNameInjectable from "../../common/vars/sentry-dsn-url.injectable";
import type { FakeExtensionOptions } from "../../renderer/components/test-utils/get-extension-fake"; 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", () => { describe("preferences - navigation to telemetry preferences", () => {
let builder: ApplicationBuilder; let builder: ApplicationBuilder;
@ -43,7 +43,10 @@ describe("preferences - navigation to telemetry preferences", () => {
}); });
it("does not show link for 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(); expect(actual).toBeNull();
}); });
@ -60,7 +63,10 @@ describe("preferences - navigation to telemetry preferences", () => {
}); });
it("shows link for 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(); expect(actual).not.toBeNull();
}); });
@ -84,11 +90,12 @@ describe("preferences - navigation to telemetry preferences", () => {
}); });
it("shows extension telemetry preference items", () => { it("shows extension telemetry preference items", () => {
const actual = rendered.getByTestId( const actual =
"telemetry-preference-item-for-some-telemetry-preference-item-id", 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(); expect(actual).toBeNull();
}); });
@ -139,9 +149,11 @@ describe("preferences - navigation to telemetry preferences", () => {
}); });
it("allows configuration of automatic error reporting", () => { 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", () => { 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 }) => ( export const getPreferencePage = (label: string): PreferencePageComponent => ({ children, item }) => (
<section id={item.id} data-preference-page-test={item.id}> <section id={item.id} data-preference-page-test={item.id}>
<h2>{label}</h2> <h2 data-preference-page-title-test={true}>{label}</h2>
{children} {children}
</section> </section>

View File

@ -11,7 +11,6 @@ import preferencesRouteInjectable from "../../common/preferences-route.injectabl
import { filter, map } from "lodash/fp"; import { filter, map } from "lodash/fp";
import { pipeline } from "@ogre-tools/fp"; import { pipeline } from "@ogre-tools/fp";
import { normalizeComposite } from "../../../application-menu/main/menu-items/get-composite/normalize-composite/normalize-composite"; 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 preferencesCompositeInjectable from "./preferences-composite.injectable";
import type { PreferenceTabsRoot } from "./preference-tab-root"; import type { PreferenceTabsRoot } from "./preference-tab-root";
@ -26,12 +25,18 @@ const currentPreferenceTabCompositeInjectable = getInjectable({
return computed(() => { return computed(() => {
const { preferenceTabId } = routePathParameters.get(); const { preferenceTabId } = routePathParameters.get();
return pipeline( const tabComposites = pipeline(
normalizeComposite(preferencesComposite.get()), normalizeComposite(preferencesComposite.get()),
map(([, composite]) => composite), map(([, composite]) => composite),
filter(isPreferenceTab), 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. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getInjectionToken } from "@ogre-tools/injectable"; import { getInjectionToken } from "@ogre-tools/injectable";
import type { IComputedValue } from "mobx";
import type React from "react"; import type React from "react";
export type PreferenceItemComponent = React.ComponentType<{ export type PreferenceItemComponent = React.ComponentType<{
@ -21,7 +22,7 @@ export interface PreferenceTab {
pathId: string; pathId: string;
label: string; label: string;
orderNumber: number; orderNumber: number;
isShown?: boolean; isShown?: IComputedValue<boolean> | boolean;
} }
export interface PreferenceTabGroup { export interface PreferenceTabGroup {
@ -30,7 +31,7 @@ export interface PreferenceTabGroup {
parentId: "preference-tabs"; parentId: "preference-tabs";
label: string; label: string;
orderNumber: number; orderNumber: number;
isShown?: boolean; isShown?: IComputedValue<boolean> | boolean;
iconName?: string; iconName?: string;
} }
@ -38,7 +39,7 @@ export interface PreferencePage {
kind: "page"; kind: "page";
id: string; id: string;
parentId: string; parentId: string;
isShown?: boolean; isShown?: IComputedValue<boolean> | boolean;
childrenSeparator?: () => React.ReactElement; childrenSeparator?: () => React.ReactElement;
Component: PreferencePageComponent; Component: PreferencePageComponent;
} }
@ -47,7 +48,7 @@ export interface PreferenceGroup {
kind: "group"; kind: "group";
id: string; id: string;
parentId: string; parentId: string;
isShown?: boolean; isShown?: IComputedValue<boolean> | boolean;
childrenSeparator?: () => React.ReactElement; childrenSeparator?: () => React.ReactElement;
} }
@ -57,7 +58,7 @@ export interface PreferenceItem {
id: string; id: string;
parentId: string; parentId: string;
orderNumber: number; orderNumber: number;
isShown?: boolean; isShown?: IComputedValue<boolean> | boolean;
childrenSeparator?: () => React.ReactElement; childrenSeparator?: () => React.ReactElement;
} }

View File

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

View File

@ -11,6 +11,8 @@ import getComposite from "../../../application-menu/main/menu-items/get-composit
import { filter } from "lodash/fp"; import { filter } from "lodash/fp";
import { pipeline } from "@ogre-tools/fp"; import { pipeline } from "@ogre-tools/fp";
import { preferenceTabsRoot } from "./preference-tab-root"; import { preferenceTabsRoot } from "./preference-tab-root";
import logErrorInjectable from "../../../../common/log-error.injectable";
import { isBoolean } from "../../../../common/utils";
const preferencesCompositeInjectable = getInjectable({ const preferencesCompositeInjectable = getInjectable({
id: "preferences-composite", id: "preferences-composite",
@ -18,17 +20,41 @@ const preferencesCompositeInjectable = getInjectable({
instantiate: (di) => { instantiate: (di) => {
const computedInjectMany = di.inject(computedInjectManyInjectable); const computedInjectMany = di.inject(computedInjectManyInjectable);
const preferenceItems = computedInjectMany(preferenceItemInjectionToken); const preferenceItems = computedInjectMany(preferenceItemInjectionToken);
const logError = di.inject(logErrorInjectable);
return computed(() => return computed(() =>
pipeline( pipeline(
[preferenceTabsRoot, ...preferenceItems.get()], [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; export default preferencesCompositeInjectable;

View File

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

View File

@ -18,7 +18,7 @@ import { PreferencesNavigation } from "./preference-navigation/preferences-navig
interface Dependencies { interface Dependencies {
closePreferences: () => void; closePreferences: () => void;
pageComposite: IComputedValue<Composite<PreferenceTab>>; pageComposite: IComputedValue<Composite<PreferenceTab> | undefined>;
} }
const NonInjectedPreferences = observer(({ const NonInjectedPreferences = observer(({
@ -35,7 +35,16 @@ const NonInjectedPreferences = observer(({
closeButtonProps={{ "data-testid": "close-preferences" }} closeButtonProps={{ "data-testid": "close-preferences" }}
back={closePreferences} 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> </SettingLayout>
); );
}); });
@ -58,13 +67,14 @@ const toPreferenceItemHierarchy = (composite: Composite<PreferenceTypes>) => {
const Component = value.Component; const Component = value.Component;
return ( return (
<Component> <div data-preference-item-test={composite.id}>
<Map items={composite.children} getSeparator={value.childrenSeparator}> <Component>
{toPreferenceItemHierarchy} <Map items={composite.children} getSeparator={value.childrenSeparator}>
</Map> {toPreferenceItemHierarchy}
</Component> </Map>
</Component>
</div>
); );
} }
// eslint-disable-next-line no-fallthrough // 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 React from "react";
import type { RegisteredAppPreference } from "./app-preferences/app-preference-registration"; import type { RegisteredAppPreference } from "./app-preferences/app-preference-registration";
import extensionPreferencesModelInjectable from "./extension-preference-model.injectable"; 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 { interface Dependencies {
model: IComputedValue<{ model: IComputedValue<{
@ -36,10 +36,9 @@ const NonInjectedExtensions = ({ model }: Dependencies) => {
</div> </div>
)} )}
{preferenceItems.map((preferenceItem, index) => ( {preferenceItems.map((preferenceItem, index) => (
<ExtensionSettings <ExtensionPreferenceItem
key={`${preferenceItem.id}-${index}`} key={`${preferenceItem.id}-${index}`}
setting={preferenceItem} registration={preferenceItem}
size="small"
data-testid={`extension-preference-item-for-${preferenceItem.id}`} data-testid={`extension-preference-item-for-${preferenceItem.id}`}
/> />
))} ))}