Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding search bar to extensions menu #305

Merged
merged 31 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f5ad90d
extrated search bar from TopicList to be its own component
ctw-joao-luis Dec 12, 2024
5710b7c
update on SearchBar component and TopicList use of it
ctw-joao-luis Dec 12, 2024
56add80
added search bar to extensions menu
ctw-joao-luis Dec 13, 2024
834a0a2
added searching logic to search bar on extensions
ctw-joao-luis Dec 13, 2024
9d2ae92
separting styles to its own files
ctw-joao-luis Dec 13, 2024
b04412f
added padding on searchBar | implemented translations on extensionsSe…
ctw-joao-luis Dec 13, 2024
022d900
extrated search bar from TopicList to be its own component
ctw-joao-luis Dec 12, 2024
7c9dc2b
update on SearchBar component and TopicList use of it
ctw-joao-luis Dec 12, 2024
d8651ab
added search bar to extensions menu
ctw-joao-luis Dec 13, 2024
f71fd3e
added searching logic to search bar on extensions
ctw-joao-luis Dec 13, 2024
c820437
separting styles to its own files
ctw-joao-luis Dec 13, 2024
7653db3
Merge branch 'feature/extensions-menu-search-bar' of github.com:Licht…
ctw-joao-luis Dec 13, 2024
eacec98
SearchBar code restructuring
ctw-joao-luis Dec 17, 2024
2295a97
ExtensionListEntry unit tests
ctw-joao-luis Dec 17, 2024
72a27bb
ExtensionListEntry lint and test fix
ctw-joao-luis Dec 18, 2024
3b32af1
code structure fix
ctw-joao-luis Dec 18, 2024
dc60d60
SearchBar unit tests
ctw-joao-luis Dec 18, 2024
adab744
Adding tests for TopicList.tsx
laisspportugal Dec 18, 2024
55cbf22
Fix lint:ci
laisspportugal Dec 18, 2024
84ffb77
Fix headers
laisspportugal Dec 18, 2024
7b019ee
Edit expect form tests to avoid lint errors
laisspportugal Dec 18, 2024
ab21107
Added ExtensionList unit tests
ctw-joao-luis Dec 18, 2024
98eb315
Moving logic and tests to another file
laisspportugal Dec 18, 2024
66be4c2
Merge branch 'feature/extensions-menu-search-bar' of https://github.c…
laisspportugal Dec 18, 2024
ddeee6c
Added ExtensionSettings unit tests
ctw-joao-luis Dec 19, 2024
249e6f3
Merge branch 'feature/extensions-menu-search-bar' of github.com:Licht…
ctw-joao-luis Dec 19, 2024
6035bc4
more ExtensionSettings tests added
ctw-joao-luis Dec 19, 2024
a9e9cd1
add tests to useExtensionSettings
aneuwald-ctw Dec 19, 2024
636ec1c
ExtensionList test fix
ctw-joao-luis Dec 20, 2024
cda3c4c
Merge branch 'feature/extensions-menu-search-bar' of github.com:Licht…
ctw-joao-luis Dec 20, 2024
681be2b
added testing library missing
ctw-joao-luis Dec 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/suite-base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"@tanstack/react-table": "8.11.7",
"@testing-library/jest-dom": "6.6.2",
"@testing-library/react": "16.0.0",
"@testing-library/user-event": "14.5.2",
"@types/base16": "^1.0.5",
"@types/cytoscape": "^3.19.16",
"@types/geojson": "7946.0.11",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/** @jest-environment jsdom */

// SPDX-FileCopyrightText: Copyright (C) 2023-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)<[email protected]>
// SPDX-License-Identifier: MPL-2.0

import { render, screen } from "@testing-library/react";

import "@testing-library/jest-dom";
import { Immutable } from "@lichtblick/suite";
import ExtensionList from "@lichtblick/suite-base/components/ExtensionsSettings/components/ExtensionList/ExtensionList";
import { ExtensionMarketplaceDetail } from "@lichtblick/suite-base/context/ExtensionMarketplaceContext";
import BasicBuilder from "@lichtblick/suite-base/testing/builders/BasicBuilder";

import { displayNameForNamespace, generatePlaceholderList } from "./ExtensionList";

describe("ExtensionList utility functions", () => {
describe("displayNameForNamespace", () => {
it("returns 'Organization' for 'org'", () => {
expect(displayNameForNamespace("org")).toBe("Organization");
});

it("returns the namespace itself for other values", () => {
const customNamespace = BasicBuilder.string();
expect(displayNameForNamespace(customNamespace)).toBe(customNamespace);
});
});

describe("generatePlaceholderList", () => {
it("renders a placeholder list with the given message", () => {
const message = BasicBuilder.string();
render(generatePlaceholderList(message));
expect(screen.getByText(message)).toBeInTheDocument();
});

it("renders an empty list item when no message is provided", () => {
render(generatePlaceholderList());
expect(screen.getByRole("listitem")).toBeInTheDocument();
});
});
});

describe("ExtensionList Component", () => {
const mockNamespace = "org";
const mockEntries = [
{
id: "1",
name: "Extension",
description: "Description of Extension 1",
publisher: "Publisher 1",
version: "1.0.0",
qualifiedName: "org.extension1",
homepage: BasicBuilder.string(),
license: BasicBuilder.string(),
},
{
id: "2",
name: "Extension2",
description: "Description of Extension 2",
publisher: "Publisher 2",
version: "1.0.0",
qualifiedName: "org.extension2",
homepage: BasicBuilder.string(),
license: BasicBuilder.string(),
},
];
const mockFilterText = "Extension";
const mockSelectExtension = jest.fn();

const emptyMockEntries: Immutable<ExtensionMarketplaceDetail>[] = [];

it("renders the list of extensions correctly", () => {
render(
<ExtensionList
namespace={mockNamespace}
entries={mockEntries}
filterText={mockFilterText}
selectExtension={mockSelectExtension}
/>,
);
//Since namespace passed was 'org' displayNameForNamespace() transformed it to 'Organization'
expect(screen.getByText("Organization")).toBeInTheDocument();

//finds 2 elements that represent the entries from mockEntries
const elements = screen.getAllByText("Extension");
expect(elements.length).toEqual(2);
});

it("renders 'No extensions found' message when entries are empty and there's filterText", () => {
const randomSearchValue = BasicBuilder.string();
render(
<ExtensionList
namespace={mockNamespace}
entries={emptyMockEntries}
filterText={randomSearchValue}
selectExtension={mockSelectExtension}
/>,
);

expect(screen.getByText("No extensions found")).toBeInTheDocument();
});

it("renders 'No extensions available' message when entries are empty", () => {
render(
<ExtensionList
namespace={mockNamespace}
entries={emptyMockEntries}
filterText=""
selectExtension={mockSelectExtension}
/>,
);

expect(screen.getByText("No extensions available")).toBeInTheDocument();
});

it("calls selectExtension with the correct parameters when an entry is clicked", () => {
render(
<ExtensionList
namespace={mockNamespace}
entries={mockEntries}
filterText=""
selectExtension={mockSelectExtension}
/>,
);

const firstEntry = screen.getByText("Extension");
firstEntry.click();

expect(mockSelectExtension).toHaveBeenCalledWith({
installed: true,
entry: mockEntries[0],
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-FileCopyrightText: Copyright (C) 2023-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)<[email protected]>
// SPDX-License-Identifier: MPL-2.0

import { List, ListItem, ListItemText, Typography } from "@mui/material";
import { useTranslation } from "react-i18next";

import { Immutable } from "@lichtblick/suite";
import { FocusedExtension } from "@lichtblick/suite-base/components/ExtensionsSettings/types";
import Stack from "@lichtblick/suite-base/components/Stack";
import { ExtensionMarketplaceDetail } from "@lichtblick/suite-base/context/ExtensionMarketplaceContext";

import ExtensionListEntry from "../ExtensionListEntry/ExtensionListEntry";

export function displayNameForNamespace(namespace: string): string {
if (namespace === "org") {
return "Organization";
} else {
return namespace;
}
}

export function generatePlaceholderList(message?: string): React.ReactElement {
return (
<List>
<ListItem>
<ListItemText primary={message} />
</ListItem>
</List>
);
}

type ExtensionListProps = {
namespace: string;
entries: Immutable<ExtensionMarketplaceDetail>[];
filterText: string;
selectExtension: (newFocusedExtension: FocusedExtension) => void;
};

export default function ExtensionList({
namespace,
entries,
filterText,
selectExtension,
}: ExtensionListProps): React.JSX.Element {
const { t } = useTranslation("extensionsSettings");

const renderComponent = () => {
if (entries.length === 0 && filterText) {
return generatePlaceholderList(t("noExtensionsFound"));
} else if (entries.length === 0) {
return generatePlaceholderList(t("noExtensionsAvailable"));
}
return (
<>
{entries.map((entry) => (
<ExtensionListEntry
key={entry.id}
entry={entry}
onClick={() => {
selectExtension({ installed: true, entry });
}}
searchText={filterText}
/>
))}
</>
);
};

return (
<List key={namespace}>
<Stack paddingY={0} paddingX={2}>
<Typography component="li" variant="overline" color="text.secondary">
{displayNameForNamespace(namespace)}
</Typography>
</Stack>
{renderComponent()}
</List>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-FileCopyrightText: Copyright (C) 2023-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)<[email protected]>
// SPDX-License-Identifier: MPL-2.0

import { makeStyles } from "tss-react/mui";

export const useStyles = makeStyles()((theme) => ({
listItemButton: {
"&:hover": { color: theme.palette.primary.main },
},
}));
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/** @jest-environment jsdom */

// SPDX-FileCopyrightText: Copyright (C) 2023-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)<[email protected]>
// SPDX-License-Identifier: MPL-2.0
import { render, screen, fireEvent } from "@testing-library/react";

import { Immutable } from "@lichtblick/suite";
import { ExtensionMarketplaceDetail } from "@lichtblick/suite-base/context/ExtensionMarketplaceContext";
import BasicBuilder from "@lichtblick/suite-base/testing/builders/BasicBuilder";
import "@testing-library/jest-dom";

import ExtensionListEntry from "./ExtensionListEntry";

describe("ExtensionListEntry Component", () => {
const mockEntry: Immutable<ExtensionMarketplaceDetail> = {
id: BasicBuilder.string(),
name: BasicBuilder.string(),
qualifiedName: BasicBuilder.string(),
description: BasicBuilder.string(),
publisher: BasicBuilder.string(),
homepage: BasicBuilder.string(),
license: BasicBuilder.string(),
version: BasicBuilder.string(),
};

const mockOnClick = jest.fn();

it("renders primary text with name and highlights search text", () => {
render(
<ExtensionListEntry entry={mockEntry} searchText={mockEntry.name} onClick={mockOnClick} />,
);

const name = screen.getByText(new RegExp(mockEntry.name, "i"));
expect(name).toBeInTheDocument();

const highlightedText = screen.getByText(new RegExp(mockEntry.name, "i"));
expect(highlightedText).toBeInTheDocument();
expect(highlightedText.tagName).toBe("SPAN");
});

it("renders secondary text with description and publisher", () => {
render(
<ExtensionListEntry entry={mockEntry} searchText={mockEntry.name} onClick={mockOnClick} />,
);

const description = screen.getByText(new RegExp(mockEntry.description, "i"));
expect(description).toBeInTheDocument();

const publisher = screen.getByText(new RegExp(mockEntry.publisher, "i"));
expect(publisher).toBeInTheDocument();
});

it("displays version next to name", () => {
render(
<ExtensionListEntry entry={mockEntry} searchText={mockEntry.name} onClick={mockOnClick} />,
);

// Check for version
const version = screen.getByText(new RegExp(mockEntry.version, "i"));
expect(version).toBeInTheDocument();
});

it("calls onClick when ListItemButton is clicked", () => {
render(<ExtensionListEntry entry={mockEntry} searchText="" onClick={mockOnClick} />);

const button = screen.getByRole("button");
fireEvent.click(button);

expect(mockOnClick).toHaveBeenCalledTimes(1);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: Copyright (C) 2023-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)<[email protected]>
// SPDX-License-Identifier: MPL-2.0

import { ListItem, ListItemButton, ListItemText, Typography } from "@mui/material";

import { Immutable } from "@lichtblick/suite";
import Stack from "@lichtblick/suite-base/components/Stack";
import TextHighlight from "@lichtblick/suite-base/components/TextHighlight";
import { ExtensionMarketplaceDetail } from "@lichtblick/suite-base/context/ExtensionMarketplaceContext";

import { useStyles } from "./ExtensionListEntry.style";

type Props = {
entry: Immutable<ExtensionMarketplaceDetail>;
onClick: () => void;
searchText: string;
};

export default function ExtensionListEntry({
entry: { id, description, name, publisher, version },
searchText,
onClick,
}: Props): React.JSX.Element {
const { classes } = useStyles();
return (
<ListItem disablePadding key={id}>
<ListItemButton className={classes.listItemButton} onClick={onClick}>
<ListItemText
disableTypography
primary={
<Stack direction="row" alignItems="baseline" gap={0.5}>
<Typography variant="subtitle2" fontWeight={600}>
<TextHighlight targetStr={name} searchText={searchText} />
</Typography>
<Typography variant="caption" color="text.secondary">
{version}
</Typography>
</Stack>
}
secondary={
<Stack gap={0.5}>
<Typography variant="body2" color="text.secondary">
{description}
</Typography>
<Typography color="text.primary" variant="body2">
{publisher}
</Typography>
</Stack>
}
/>
</ListItemButton>
</ListItem>
);
}
Loading
Loading