mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-10-03 05:16:50 +03:00
add default icon, observe measure div, fix tests
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
import type { FrigateApp } from "../fixtures/frigate-test";
|
||||||
|
|
||||||
|
// On mobile the System tabs sit in an OverflowStrip, which keeps an inert copy
|
||||||
|
// of every tab for measurement and hides the ones that do not fit behind a
|
||||||
|
// kebab. The selected tab always stays in the strip.
|
||||||
|
|
||||||
|
export function systemTab(frigateApp: FrigateApp, name: string) {
|
||||||
|
return frigateApp.page
|
||||||
|
.locator(`[aria-label="Select ${name}" i]:not([inert] *)`)
|
||||||
|
.first();
|
||||||
|
}
|
||||||
@@ -7,12 +7,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { test, expect } from "../fixtures/frigate-test";
|
import { test, expect } from "../fixtures/frigate-test";
|
||||||
|
import { systemTab } from "../helpers/system-tabs";
|
||||||
import { viewerProfile } from "../fixtures/mock-data/profile";
|
import { viewerProfile } from "../fixtures/mock-data/profile";
|
||||||
|
|
||||||
test.describe("Auth — admin access @high", () => {
|
test.describe("Auth — admin access @high", () => {
|
||||||
test("admin /system renders general tab", async ({ frigateApp }) => {
|
test("admin /system renders general tab", async ({ frigateApp }) => {
|
||||||
await frigateApp.goto("/system");
|
await frigateApp.goto("/system");
|
||||||
await expect(frigateApp.page.getByLabel("Select general")).toBeVisible({
|
await expect(systemTab(frigateApp, "general")).toBeVisible({
|
||||||
timeout: 15_000,
|
timeout: 15_000,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -28,7 +29,7 @@ test.describe("Auth — admin access @high", () => {
|
|||||||
|
|
||||||
test("admin /logs renders frigate tab", async ({ frigateApp }) => {
|
test("admin /logs renders frigate tab", async ({ frigateApp }) => {
|
||||||
await frigateApp.goto("/logs");
|
await frigateApp.goto("/logs");
|
||||||
await expect(frigateApp.page.getByLabel("Select frigate")).toBeVisible({
|
await expect(systemTab(frigateApp, "frigate")).toBeVisible({
|
||||||
timeout: 5_000,
|
timeout: 5_000,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -287,3 +287,22 @@ test.describe("Live mobile layout @critical @mobile", () => {
|
|||||||
await expect(frigateApp.page.locator("body")).toBeVisible();
|
await expect(frigateApp.page.locator("body")).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.describe("Live camera groups @medium", () => {
|
||||||
|
test("a group with an invalid icon renders a fallback icon", async ({
|
||||||
|
frigateApp,
|
||||||
|
}) => {
|
||||||
|
await frigateApp.installDefaults({
|
||||||
|
config: {
|
||||||
|
camera_groups: {
|
||||||
|
outdoor: { cameras: ["front_door"], icon: "generic" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await frigateApp.goto("/");
|
||||||
|
const group = frigateApp.page
|
||||||
|
.locator('[aria-label="Camera Groups"]:not([inert] *)')
|
||||||
|
.first();
|
||||||
|
await expect(group.locator("svg")).toBeVisible({ timeout: 10_000 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { test, expect } from "../fixtures/frigate-test";
|
import { test, expect } from "../fixtures/frigate-test";
|
||||||
|
import { systemTab } from "../helpers/system-tabs";
|
||||||
import { viewerProfile } from "../fixtures/mock-data/profile";
|
import { viewerProfile } from "../fixtures/mock-data/profile";
|
||||||
|
|
||||||
const NOW = Math.floor(Date.now() / 1000);
|
const NOW = Math.floor(Date.now() / 1000);
|
||||||
@@ -55,7 +56,7 @@ test.describe("System — Health tab @medium", () => {
|
|||||||
});
|
});
|
||||||
await frigateApp.goto("/system");
|
await frigateApp.goto("/system");
|
||||||
|
|
||||||
await expect(frigateApp.page.getByLabel("Select health")).toHaveAttribute(
|
await expect(systemTab(frigateApp, "health")).toHaveAttribute(
|
||||||
"data-state",
|
"data-state",
|
||||||
"on",
|
"on",
|
||||||
{ timeout: 15_000 },
|
{ timeout: 15_000 },
|
||||||
@@ -801,7 +802,7 @@ test.describe("System — Health notices sources @medium", () => {
|
|||||||
stats: QUIET_STATS,
|
stats: QUIET_STATS,
|
||||||
});
|
});
|
||||||
await frigateApp.goto("/system#health");
|
await frigateApp.goto("/system#health");
|
||||||
await expect(frigateApp.page.getByLabel("Select health")).toHaveAttribute(
|
await expect(systemTab(frigateApp, "health")).toHaveAttribute(
|
||||||
"data-state",
|
"data-state",
|
||||||
"on",
|
"on",
|
||||||
{ timeout: 15_000 },
|
{ timeout: 15_000 },
|
||||||
|
|||||||
@@ -11,16 +11,7 @@ import {
|
|||||||
expectBodyInteractive,
|
expectBodyInteractive,
|
||||||
waitForBodyInteractive,
|
waitForBodyInteractive,
|
||||||
} from "../helpers/overlay-interaction";
|
} from "../helpers/overlay-interaction";
|
||||||
|
import { systemTab } from "../helpers/system-tabs";
|
||||||
// On mobile the tabs sit in an OverflowStrip, which keeps an inert copy of
|
|
||||||
// every tab for measurement and hides the ones that do not fit behind a kebab.
|
|
||||||
// The selected tab always stays in the strip.
|
|
||||||
|
|
||||||
function tab(frigateApp: FrigateApp, name: string) {
|
|
||||||
return frigateApp.page
|
|
||||||
.locator(`[aria-label="Select ${name}" i]:not([inert] *)`)
|
|
||||||
.first();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function selectTab(frigateApp: FrigateApp, name: string) {
|
async function selectTab(frigateApp: FrigateApp, name: string) {
|
||||||
const kebab = frigateApp.page.getByLabel("Show all tabs");
|
const kebab = frigateApp.page.getByLabel("Show all tabs");
|
||||||
@@ -34,13 +25,17 @@ async function selectTab(frigateApp: FrigateApp, name: string) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await tab(frigateApp, name).click();
|
await systemTab(frigateApp, name).click();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function expectTabActive(frigateApp: FrigateApp, name: string) {
|
async function expectTabActive(frigateApp: FrigateApp, name: string) {
|
||||||
await expect(tab(frigateApp, name)).toHaveAttribute("data-state", "on", {
|
await expect(systemTab(frigateApp, name)).toHaveAttribute(
|
||||||
timeout: 5_000,
|
"data-state",
|
||||||
});
|
"on",
|
||||||
|
{
|
||||||
|
timeout: 5_000,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
test.describe("System — tabs @medium", () => {
|
test.describe("System — tabs @medium", () => {
|
||||||
@@ -48,14 +43,14 @@ test.describe("System — tabs @medium", () => {
|
|||||||
frigateApp,
|
frigateApp,
|
||||||
}) => {
|
}) => {
|
||||||
await frigateApp.goto("/system#general");
|
await frigateApp.goto("/system#general");
|
||||||
await expect(tab(frigateApp, "general")).toHaveAttribute(
|
await expect(systemTab(frigateApp, "general")).toHaveAttribute(
|
||||||
"data-state",
|
"data-state",
|
||||||
"on",
|
"on",
|
||||||
{ timeout: 15_000 },
|
{ timeout: 15_000 },
|
||||||
);
|
);
|
||||||
if (!frigateApp.isMobile) {
|
if (!frigateApp.isMobile) {
|
||||||
await expect(tab(frigateApp, "storage")).toBeVisible();
|
await expect(systemTab(frigateApp, "storage")).toBeVisible();
|
||||||
await expect(tab(frigateApp, "cameras")).toBeVisible();
|
await expect(systemTab(frigateApp, "cameras")).toBeVisible();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -63,14 +58,14 @@ test.describe("System — tabs @medium", () => {
|
|||||||
frigateApp,
|
frigateApp,
|
||||||
}) => {
|
}) => {
|
||||||
await frigateApp.goto("/system#general");
|
await frigateApp.goto("/system#general");
|
||||||
await expect(tab(frigateApp, "general")).toHaveAttribute(
|
await expect(systemTab(frigateApp, "general")).toHaveAttribute(
|
||||||
"data-state",
|
"data-state",
|
||||||
"on",
|
"on",
|
||||||
{ timeout: 15_000 },
|
{ timeout: 15_000 },
|
||||||
);
|
);
|
||||||
await selectTab(frigateApp, "storage");
|
await selectTab(frigateApp, "storage");
|
||||||
await expectTabActive(frigateApp, "storage");
|
await expectTabActive(frigateApp, "storage");
|
||||||
await expect(tab(frigateApp, "general")).toHaveAttribute(
|
await expect(systemTab(frigateApp, "general")).toHaveAttribute(
|
||||||
"data-state",
|
"data-state",
|
||||||
"off",
|
"off",
|
||||||
);
|
);
|
||||||
@@ -78,7 +73,7 @@ test.describe("System — tabs @medium", () => {
|
|||||||
|
|
||||||
test("Cameras tab activates", async ({ frigateApp }) => {
|
test("Cameras tab activates", async ({ frigateApp }) => {
|
||||||
await frigateApp.goto("/system#general");
|
await frigateApp.goto("/system#general");
|
||||||
await expect(tab(frigateApp, "general")).toHaveAttribute(
|
await expect(systemTab(frigateApp, "general")).toHaveAttribute(
|
||||||
"data-state",
|
"data-state",
|
||||||
"on",
|
"on",
|
||||||
{ timeout: 15_000 },
|
{ timeout: 15_000 },
|
||||||
@@ -91,7 +86,7 @@ test.describe("System — tabs @medium", () => {
|
|||||||
frigateApp,
|
frigateApp,
|
||||||
}) => {
|
}) => {
|
||||||
await frigateApp.goto("/system#general");
|
await frigateApp.goto("/system#general");
|
||||||
await expect(tab(frigateApp, "general")).toHaveAttribute(
|
await expect(systemTab(frigateApp, "general")).toHaveAttribute(
|
||||||
"data-state",
|
"data-state",
|
||||||
"on",
|
"on",
|
||||||
{ timeout: 15_000 },
|
{ timeout: 15_000 },
|
||||||
@@ -112,7 +107,7 @@ test.describe("System — tabs @medium", () => {
|
|||||||
frigateApp,
|
frigateApp,
|
||||||
}) => {
|
}) => {
|
||||||
await frigateApp.goto("/system#general");
|
await frigateApp.goto("/system#general");
|
||||||
await expect(tab(frigateApp, "general")).toHaveAttribute(
|
await expect(systemTab(frigateApp, "general")).toHaveAttribute(
|
||||||
"data-state",
|
"data-state",
|
||||||
"on",
|
"on",
|
||||||
{ timeout: 15_000 },
|
{ timeout: 15_000 },
|
||||||
@@ -133,7 +128,7 @@ test.describe("System — tabs @medium", () => {
|
|||||||
} else {
|
} else {
|
||||||
// Mobile: tab activation (data-state "on") already asserted above.
|
// Mobile: tab activation (data-state "on") already asserted above.
|
||||||
// Additionally confirm general tab is no longer the active tab.
|
// Additionally confirm general tab is no longer the active tab.
|
||||||
await expect(tab(frigateApp, "general")).toHaveAttribute(
|
await expect(systemTab(frigateApp, "general")).toHaveAttribute(
|
||||||
"data-state",
|
"data-state",
|
||||||
"off",
|
"off",
|
||||||
{ timeout: 5_000 },
|
{ timeout: 5_000 },
|
||||||
@@ -143,7 +138,7 @@ test.describe("System — tabs @medium", () => {
|
|||||||
|
|
||||||
test("cameras tab renders each configured camera", async ({ frigateApp }) => {
|
test("cameras tab renders each configured camera", async ({ frigateApp }) => {
|
||||||
await frigateApp.goto("/system#general");
|
await frigateApp.goto("/system#general");
|
||||||
await expect(tab(frigateApp, "general")).toHaveAttribute(
|
await expect(systemTab(frigateApp, "general")).toHaveAttribute(
|
||||||
"data-state",
|
"data-state",
|
||||||
"on",
|
"on",
|
||||||
{ timeout: 15_000 },
|
{ timeout: 15_000 },
|
||||||
@@ -170,7 +165,7 @@ test.describe("System — tabs @medium", () => {
|
|||||||
config: { semantic_search: { enabled: true } },
|
config: { semantic_search: { enabled: true } },
|
||||||
});
|
});
|
||||||
await frigateApp.goto("/system#general");
|
await frigateApp.goto("/system#general");
|
||||||
await expect(tab(frigateApp, "general")).toHaveAttribute(
|
await expect(systemTab(frigateApp, "general")).toHaveAttribute(
|
||||||
"data-state",
|
"data-state",
|
||||||
"on",
|
"on",
|
||||||
{ timeout: 15_000 },
|
{ timeout: 15_000 },
|
||||||
@@ -238,14 +233,14 @@ test.describe("System — mobile @medium @mobile", () => {
|
|||||||
|
|
||||||
test("tabs render at mobile viewport", async ({ frigateApp }) => {
|
test("tabs render at mobile viewport", async ({ frigateApp }) => {
|
||||||
await frigateApp.goto("/system#general");
|
await frigateApp.goto("/system#general");
|
||||||
await expect(tab(frigateApp, "general")).toBeVisible({
|
await expect(systemTab(frigateApp, "general")).toBeVisible({
|
||||||
timeout: 15_000,
|
timeout: 15_000,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("switching tabs works at mobile viewport", async ({ frigateApp }) => {
|
test("switching tabs works at mobile viewport", async ({ frigateApp }) => {
|
||||||
await frigateApp.goto("/system#general");
|
await frigateApp.goto("/system#general");
|
||||||
await expect(tab(frigateApp, "general")).toHaveAttribute(
|
await expect(systemTab(frigateApp, "general")).toHaveAttribute(
|
||||||
"data-state",
|
"data-state",
|
||||||
"on",
|
"on",
|
||||||
{ timeout: 15_000 },
|
{ timeout: 15_000 },
|
||||||
@@ -258,7 +253,7 @@ test.describe("System — mobile @medium @mobile", () => {
|
|||||||
frigateApp,
|
frigateApp,
|
||||||
}) => {
|
}) => {
|
||||||
await frigateApp.goto("/system#general");
|
await frigateApp.goto("/system#general");
|
||||||
await expect(tab(frigateApp, "general")).toHaveAttribute(
|
await expect(systemTab(frigateApp, "general")).toHaveAttribute(
|
||||||
"data-state",
|
"data-state",
|
||||||
"on",
|
"on",
|
||||||
{ timeout: 15_000 },
|
{ timeout: 15_000 },
|
||||||
|
|||||||
@@ -181,9 +181,14 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
|
|||||||
setGroup(name, group != "default");
|
setGroup(name, group != "default");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{config && config.icon && isValidIconName(config.icon) && (
|
<IconRenderer
|
||||||
<IconRenderer icon={LuIcons[config.icon]} className="size-5" />
|
icon={
|
||||||
)}
|
isValidIconName(config.icon)
|
||||||
|
? LuIcons[config.icon]
|
||||||
|
: LuIcons.LuFolder
|
||||||
|
}
|
||||||
|
className="size-5"
|
||||||
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
)),
|
)),
|
||||||
];
|
];
|
||||||
@@ -264,12 +269,14 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
|
|||||||
onMouseEnter={() => showTooltip(name)}
|
onMouseEnter={() => showTooltip(name)}
|
||||||
onMouseLeave={() => showTooltip(undefined)}
|
onMouseLeave={() => showTooltip(undefined)}
|
||||||
>
|
>
|
||||||
{config && config.icon && isValidIconName(config.icon) && (
|
<IconRenderer
|
||||||
<IconRenderer
|
icon={
|
||||||
icon={LuIcons[config.icon]}
|
isValidIconName(config.icon)
|
||||||
className="size-4"
|
? LuIcons[config.icon]
|
||||||
/>
|
: LuIcons.LuFolder
|
||||||
)}
|
}
|
||||||
|
className="size-4"
|
||||||
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipPortal>
|
<TooltipPortal>
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ export default function OverflowStrip({
|
|||||||
|
|
||||||
const observer = new ResizeObserver(compute);
|
const observer = new ResizeObserver(compute);
|
||||||
observer.observe(wrapper);
|
observer.observe(wrapper);
|
||||||
|
observer.observe(measure);
|
||||||
|
|
||||||
return () => observer.disconnect();
|
return () => observer.disconnect();
|
||||||
}, [items.length, gapClassName]);
|
}, [items.length, gapClassName]);
|
||||||
|
|||||||
Reference in New Issue
Block a user