mirror of
https://github.com/home-assistant/frontend.git
synced 2026-08-04 21:52:59 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d059777cc3 | |||
| 6e8a8fda6a | |||
| ea2e959d06 |
@@ -106,6 +106,7 @@ class HaPanelConfig extends HassRouterPage {
|
||||
integrations: {
|
||||
tag: "ha-config-integrations",
|
||||
load: () => import("./integrations/ha-config-integrations"),
|
||||
waitForReady: true,
|
||||
},
|
||||
labels: {
|
||||
tag: "ha-config-labels",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { mdiFilterVariant, mdiPlus } from "@mdi/js";
|
||||
import { consume } from "@lit/context";
|
||||
import type { IFuseOptions } from "fuse.js";
|
||||
import Fuse from "fuse.js";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
@@ -54,6 +55,10 @@ import type { ImprovDiscoveredDevice } from "../../../external_app/external_mess
|
||||
import "../../../layouts/hass-loading-screen";
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
import type { HassTabsSubpage } from "../../../layouts/hass-tabs-subpage";
|
||||
import {
|
||||
childPanelReadyContext,
|
||||
type RegisterChildPanelReady,
|
||||
} from "../../../layouts/panel-ready";
|
||||
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
@@ -168,6 +173,17 @@ class HaConfigIntegrationsDashboard extends KeyboardShortcutMixin(
|
||||
|
||||
@query("hass-tabs-subpage") private _tabsSubpage?: HassTabsSubpage;
|
||||
|
||||
private _resolveInitialRender?: () => void;
|
||||
|
||||
private _initialRenderComplete = new Promise<void>((resolve) => {
|
||||
this._resolveInitialRender = resolve;
|
||||
});
|
||||
|
||||
private _childReadyRegistered = false;
|
||||
|
||||
@consume({ context: childPanelReadyContext, subscribe: true })
|
||||
private _registerChildPanelReady?: RegisterChildPanelReady;
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
window.removeEventListener(
|
||||
@@ -388,6 +404,10 @@ class HaConfigIntegrationsDashboard extends KeyboardShortcutMixin(
|
||||
|
||||
protected updated(changed: PropertyValues<this>) {
|
||||
super.updated(changed);
|
||||
if (!this._childReadyRegistered && this._registerChildPanelReady) {
|
||||
this._registerChildPanelReady(this._initialRenderComplete);
|
||||
this._childReadyRegistered = true;
|
||||
}
|
||||
if (changed.has("route")) {
|
||||
this._handleRouteChanged();
|
||||
}
|
||||
@@ -415,6 +435,9 @@ class HaConfigIntegrationsDashboard extends KeyboardShortcutMixin(
|
||||
}
|
||||
|
||||
if (this.configEntries && this.configEntriesInProgress) {
|
||||
this._resolveInitialRender?.();
|
||||
this._resolveInitialRender = undefined;
|
||||
|
||||
const activeElement = deepActiveElement();
|
||||
|
||||
if (
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import type { DataEntryFlowProgress } from "../../../data/data_entry_flow";
|
||||
import { domainToName } from "../../../data/integration";
|
||||
import "../../../layouts/hass-loading-screen";
|
||||
import { ChildPanelReady } from "../../../layouts/panel-ready";
|
||||
import type { RouterOptions } from "../../../layouts/hass-router-page";
|
||||
import { HassRouterPage } from "../../../layouts/hass-router-page";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
@@ -70,6 +71,11 @@ class HaConfigIntegrations extends SubscribeMixin(HassRouterPage) {
|
||||
|
||||
private _loadTranslationsPromise?: Promise<LocalizeFunc>;
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
new ChildPanelReady(this);
|
||||
}
|
||||
|
||||
public hassSubscribe() {
|
||||
return [
|
||||
subscribeConfigEntries(
|
||||
|
||||
+82
-81
@@ -4,7 +4,7 @@
|
||||
* Run with:
|
||||
* yarn test:e2e:app
|
||||
*/
|
||||
import { test, expect, type Page } from "@playwright/test";
|
||||
import { test, expect } from "@playwright/test";
|
||||
import {
|
||||
appSidebar,
|
||||
appSidebarConfig,
|
||||
@@ -161,65 +161,100 @@ test.describe("Quick search", () => {
|
||||
|
||||
defineRouteSmokeTests(appRouteSmokeGroups);
|
||||
|
||||
const assertInitialReadiness = async (
|
||||
page: Page,
|
||||
options: {
|
||||
test("keeps the launch screen until initial panel content renders", async ({
|
||||
page,
|
||||
}) => {
|
||||
const cases: {
|
||||
name: string;
|
||||
path: string;
|
||||
loadingSelector: string;
|
||||
resolver:
|
||||
"rejectMediaBrowse" | "resolveCalendarRegistry" | "resolveMediaBrowse";
|
||||
readySelector: string;
|
||||
}
|
||||
) => {
|
||||
await goToPanel(page, options.path);
|
||||
|
||||
const launchScreen = page.locator("#ha-launch-screen");
|
||||
await expect(launchScreen).toBeAttached({ timeout: QUICK_TIMEOUT });
|
||||
await expect(page.locator(options.loadingSelector)).toBeAttached({
|
||||
timeout: QUICK_TIMEOUT,
|
||||
});
|
||||
|
||||
await page.evaluate((resolver) => window[resolver]?.(), options.resolver);
|
||||
|
||||
await expect(page.locator(options.readySelector)).toBeAttached({
|
||||
timeout: PANEL_TIMEOUT,
|
||||
});
|
||||
await expect(launchScreen).not.toBeAttached({ timeout: QUICK_TIMEOUT });
|
||||
};
|
||||
|
||||
test.describe("Initial readiness", () => {
|
||||
test("keeps the launch screen until calendar content renders", async ({
|
||||
page,
|
||||
}) => {
|
||||
await assertInitialReadiness(page, {
|
||||
resolvers: (
|
||||
| "rejectMediaBrowse"
|
||||
| "resolveCalendarRegistry"
|
||||
| "resolveConfigEntries"
|
||||
| "resolveConfigEntriesInProgress"
|
||||
| "resolveGeneratedDashboard"
|
||||
| "resolveLovelaceConfig"
|
||||
| "resolveMediaBrowse"
|
||||
)[];
|
||||
}[] = [
|
||||
{
|
||||
name: "calendar",
|
||||
path: "/?scenario=delayed-calendar#/calendar",
|
||||
loadingSelector: "ha-panel-calendar ha-spinner",
|
||||
resolver: "resolveCalendarRegistry",
|
||||
readySelector: "ha-full-calendar",
|
||||
});
|
||||
});
|
||||
|
||||
test("keeps the launch screen until media content renders", async ({
|
||||
page,
|
||||
}) => {
|
||||
await assertInitialReadiness(page, {
|
||||
resolvers: ["resolveCalendarRegistry"],
|
||||
},
|
||||
{
|
||||
name: "media browser",
|
||||
path: "/?scenario=delayed-media-browse#/media-browser/browser",
|
||||
loadingSelector: "ha-media-player-browse > ha-spinner",
|
||||
resolver: "resolveMediaBrowse",
|
||||
readySelector: "ha-media-player-browse .no-items",
|
||||
});
|
||||
});
|
||||
|
||||
test("keeps the launch screen until a media error renders", async ({
|
||||
page,
|
||||
}) => {
|
||||
await assertInitialReadiness(page, {
|
||||
resolvers: ["resolveMediaBrowse"],
|
||||
},
|
||||
{
|
||||
name: "integrations",
|
||||
path: "/?scenario=delayed-integrations#/config/integrations",
|
||||
loadingSelector: "ha-config-integrations-dashboard hass-loading-screen",
|
||||
readySelector: "ha-config-integrations-dashboard hass-tabs-subpage",
|
||||
resolvers: ["resolveConfigEntries", "resolveConfigEntriesInProgress"],
|
||||
},
|
||||
{
|
||||
name: "media browser error",
|
||||
path: "/?scenario=delayed-media-browse-error#/media-browser/browser",
|
||||
loadingSelector: "ha-media-player-browse > ha-spinner",
|
||||
resolver: "rejectMediaBrowse",
|
||||
readySelector: "ha-media-player-browse ha-alert",
|
||||
resolvers: ["rejectMediaBrowse"],
|
||||
},
|
||||
{
|
||||
name: "generated dashboard",
|
||||
path: "/?scenario=delayed-generated-dashboard#/climate",
|
||||
loadingSelector: "#ha-launch-screen",
|
||||
readySelector: "hui-view",
|
||||
resolvers: ["resolveGeneratedDashboard"],
|
||||
},
|
||||
{
|
||||
name: "Lovelace dashboard",
|
||||
path: "/?scenario=delayed-lovelace#/lovelace",
|
||||
loadingSelector: "#ha-launch-screen",
|
||||
readySelector: "hui-card",
|
||||
resolvers: ["resolveLovelaceConfig"],
|
||||
},
|
||||
];
|
||||
|
||||
for (const readinessCase of cases) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await test.step(readinessCase.name, async () => {
|
||||
await goToPanel(page, readinessCase.path);
|
||||
|
||||
const launchScreen = page.locator("#ha-launch-screen");
|
||||
const loadingScreen = page.locator(readinessCase.loadingSelector);
|
||||
const readyContent = page.locator(readinessCase.readySelector).first();
|
||||
await expect(launchScreen).toBeAttached({ timeout: QUICK_TIMEOUT });
|
||||
await expect(loadingScreen).toBeAttached({ timeout: QUICK_TIMEOUT });
|
||||
await expect(readyContent).not.toBeAttached();
|
||||
|
||||
await readinessCase.resolvers.reduce(
|
||||
async (previousResolver, resolver, index) => {
|
||||
await previousResolver;
|
||||
await page.evaluate((resolverName) => {
|
||||
window[resolverName]?.();
|
||||
}, resolver);
|
||||
|
||||
if (index < readinessCase.resolvers.length - 1) {
|
||||
await expect(launchScreen).toBeAttached();
|
||||
await expect(loadingScreen).toBeAttached();
|
||||
await expect(readyContent).not.toBeAttached();
|
||||
}
|
||||
},
|
||||
Promise.resolve()
|
||||
);
|
||||
|
||||
await expect(readyContent).toBeAttached({ timeout: PANEL_TIMEOUT });
|
||||
await expect(launchScreen).not.toBeAttached({ timeout: QUICK_TIMEOUT });
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -227,40 +262,6 @@ test.describe("Initial readiness", () => {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test.describe("Lovelace dashboard", () => {
|
||||
test("keeps the launch screen until generated content renders", async ({
|
||||
page,
|
||||
}) => {
|
||||
await goToPanel(page, "/?scenario=delayed-generated-dashboard#/climate");
|
||||
|
||||
const launchScreen = page.locator("#ha-launch-screen");
|
||||
await expect(launchScreen).toBeAttached({ timeout: QUICK_TIMEOUT });
|
||||
await expect(page.locator("hui-view")).not.toBeAttached();
|
||||
|
||||
await page.evaluate(() => window.resolveGeneratedDashboard?.());
|
||||
|
||||
await expect(page.locator("hui-view")).toBeAttached({
|
||||
timeout: PANEL_TIMEOUT,
|
||||
});
|
||||
await expect(launchScreen).not.toBeAttached({ timeout: QUICK_TIMEOUT });
|
||||
});
|
||||
|
||||
test("keeps the launch screen until initial content renders", async ({
|
||||
page,
|
||||
}) => {
|
||||
await goToPanel(page, "/?scenario=delayed-lovelace#/lovelace");
|
||||
|
||||
const launchScreen = page.locator("#ha-launch-screen");
|
||||
await expect(launchScreen).toBeAttached({ timeout: QUICK_TIMEOUT });
|
||||
await expect(page.locator("hui-card")).not.toBeAttached();
|
||||
|
||||
await page.evaluate(() => window.resolveLovelaceConfig?.());
|
||||
|
||||
await expect(page.locator("hui-card").first()).toBeAttached({
|
||||
timeout: PANEL_TIMEOUT,
|
||||
});
|
||||
await expect(launchScreen).not.toBeAttached({ timeout: QUICK_TIMEOUT });
|
||||
});
|
||||
|
||||
test("renders cards", async ({ page }) => {
|
||||
await goToPanel(page, "/lovelace");
|
||||
// At least one card should appear
|
||||
|
||||
@@ -94,6 +94,8 @@ declare global {
|
||||
__mockHass: MockHomeAssistant;
|
||||
rejectMediaBrowse?: () => void;
|
||||
resolveCalendarRegistry?: () => void;
|
||||
resolveConfigEntries?: () => void;
|
||||
resolveConfigEntriesInProgress?: () => void;
|
||||
resolveGeneratedDashboard?: () => void;
|
||||
resolveLovelaceConfig?: () => void;
|
||||
resolveMediaBrowse?: () => void;
|
||||
|
||||
@@ -185,6 +185,25 @@ const delayedCalendarScenario: Scenario = (hass) => {
|
||||
hass.mockWS("config/entity_registry/list", () => registryPromise);
|
||||
};
|
||||
|
||||
const delayedIntegrationsScenario: Scenario = (hass) => {
|
||||
addLaunchScreen();
|
||||
|
||||
hass.mockWS(
|
||||
"config_entries/subscribe",
|
||||
(_msg, _currentHass, onChange?: (updates: unknown[]) => void) => {
|
||||
window.resolveConfigEntries = () => onChange?.([]);
|
||||
return () => undefined;
|
||||
}
|
||||
);
|
||||
hass.mockWS(
|
||||
"config_entries/flow/subscribe",
|
||||
(_msg, _currentHass, onChange?: (updates: unknown[]) => void) => {
|
||||
window.resolveConfigEntriesInProgress = () => onChange?.([]);
|
||||
return () => undefined;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const delayedMediaBrowseScenario: Scenario = (hass) => {
|
||||
addLaunchScreen();
|
||||
|
||||
@@ -230,6 +249,7 @@ export const scenarios: Record<string, Scenario> = {
|
||||
"custom-theme": customThemeScenario,
|
||||
"delayed-calendar": delayedCalendarScenario,
|
||||
"delayed-generated-dashboard": delayedGeneratedDashboardScenario,
|
||||
"delayed-integrations": delayedIntegrationsScenario,
|
||||
"delayed-media-browse": delayedMediaBrowseScenario,
|
||||
"delayed-media-browse-error": delayedMediaBrowseErrorScenario,
|
||||
"light-more-info": lightMoreInfoScenario,
|
||||
|
||||
Reference in New Issue
Block a user