fix: Move upgrade button to top of editor (#2687)
This commit is contained in:
@@ -1,13 +1,23 @@
|
||||
import { describe, expect, it, onTestFinished } from 'vitest';
|
||||
|
||||
import type { RawAdvancedCameraCardConfig } from '../../src/config/types';
|
||||
import { createLogAction } from '../../src/utils/action';
|
||||
import { deepQueryAll, getFocusedElement, pressKey, pressTab } from '../browser/dom';
|
||||
import {
|
||||
deepQuery,
|
||||
deepQueryAll,
|
||||
getFocusedElement,
|
||||
pressKey,
|
||||
pressTab,
|
||||
} from '../browser/dom';
|
||||
import { MountedCardFactory, type MountedCard } from '../browser/mounted-card';
|
||||
import {
|
||||
CARD_INITIALIZED_MESSAGE,
|
||||
createGenericCameraHASS,
|
||||
createInitializedAutomation,
|
||||
createStillImageCameraConfig,
|
||||
createStillImageCardConfig,
|
||||
getBlockNotificationText,
|
||||
isLiveMediaShowing,
|
||||
} from '../browser/test-utils';
|
||||
|
||||
// What the automation writes when it runs, written as the pattern the console
|
||||
@@ -68,7 +78,100 @@ const addTrailingControl = (): HTMLElement => {
|
||||
return control;
|
||||
};
|
||||
|
||||
// The Home Assistant dialog the card is previewed in while its configuration is
|
||||
// edited, which is what the card looks for before answering the editor's
|
||||
// diagnostics button.
|
||||
const EDIT_DIALOG_TAG_NAME = 'hui-dialog-edit-card';
|
||||
|
||||
const INIT_FAILED_ISSUE_HEADING = 'Initialization failed';
|
||||
|
||||
const DIAGNOSTICS_SELECTOR = 'advanced-camera-card-diagnostics';
|
||||
|
||||
const mountCardInEditDialog = async (
|
||||
config: RawAdvancedCameraCardConfig,
|
||||
): Promise<MountedCard> =>
|
||||
await MountedCardFactory.createFromSource(config, createGenericCameraHASS(), {
|
||||
containerTagName: EDIT_DIALOG_TAG_NAME,
|
||||
});
|
||||
|
||||
/**
|
||||
* Press the editor's diagnostics button. The editor is elsewhere in the dialog
|
||||
* rather than within the card, so the event is fired from a sibling of it.
|
||||
*/
|
||||
const toggleDiagnostics = (card: MountedCard): void => {
|
||||
const editor = document.createElement('div');
|
||||
card.card.parentElement?.append(editor);
|
||||
|
||||
editor.dispatchEvent(
|
||||
new CustomEvent('advanced-camera-card:editor:diagnostics', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}),
|
||||
);
|
||||
|
||||
editor.remove();
|
||||
};
|
||||
|
||||
const isDiagnosticsShowing = (card: MountedCard): boolean =>
|
||||
!!deepQuery(card.card, DIAGNOSTICS_SELECTOR);
|
||||
|
||||
describe('CardElementManager', () => {
|
||||
describe('should toggle diagnostics from the editor', () => {
|
||||
it('should show diagnostics over a card that could not be started', async () => {
|
||||
// A camera Home Assistant has never heard of, so the card cannot start
|
||||
// and shows an issue in place of its views.
|
||||
const card = await mountCardInEditDialog(
|
||||
createStillImageCardConfig({
|
||||
cameras: [createStillImageCameraConfig('camera.missing')],
|
||||
view: { issues: { retry_seconds: 0 } },
|
||||
}),
|
||||
);
|
||||
await card.waitForRender(
|
||||
() =>
|
||||
getBlockNotificationText(card.card).includes(INIT_FAILED_ISSUE_HEADING) ||
|
||||
null,
|
||||
'the initialization issue',
|
||||
);
|
||||
|
||||
toggleDiagnostics(card);
|
||||
|
||||
// Diagnostics is what the user is asked for when the card is broken, so
|
||||
// it must be reachable in the state the issue describes.
|
||||
await card.waitForSelector(DIAGNOSTICS_SELECTOR);
|
||||
expect(getBlockNotificationText(card.card)).not.toContain(
|
||||
INIT_FAILED_ISSUE_HEADING,
|
||||
);
|
||||
|
||||
toggleDiagnostics(card);
|
||||
|
||||
// Toggling diagnostics again just puts the issue back in front of the
|
||||
// user.
|
||||
await card.waitForRender(
|
||||
() =>
|
||||
getBlockNotificationText(card.card).includes(INIT_FAILED_ISSUE_HEADING) ||
|
||||
null,
|
||||
'the initialization issue',
|
||||
);
|
||||
expect(isDiagnosticsShowing(card)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return a started card to its default view', async () => {
|
||||
const card = await mountCardInEditDialog(createStillImageCardConfig());
|
||||
await card.events.waitForFirst('advanced-camera-card:media:loaded');
|
||||
|
||||
toggleDiagnostics(card);
|
||||
await card.waitForSelector(DIAGNOSTICS_SELECTOR);
|
||||
|
||||
toggleDiagnostics(card);
|
||||
|
||||
await card.waitForRender(
|
||||
() => isLiveMediaShowing(card.card) || null,
|
||||
'the live view',
|
||||
);
|
||||
expect(isDiagnosticsShowing(card)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be reachable by tabbing', async () => {
|
||||
const card = await mountCard();
|
||||
|
||||
|
||||
@@ -439,6 +439,7 @@ describe('CardElementManager', () => {
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
new View({ view: 'diagnostics' }),
|
||||
);
|
||||
vi.mocked(api.getViewManager().canSetViewDefault).mockReturnValue(true);
|
||||
|
||||
const dialog = createDialogWithCard(element);
|
||||
document.body.append(dialog);
|
||||
@@ -449,6 +450,31 @@ describe('CardElementManager', () => {
|
||||
expect(api.getViewManager().setViewDefault).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should reset the view when leaving diagnostics with no default view available', () => {
|
||||
const api = createCardAPI();
|
||||
const element = createCardHTMLElement();
|
||||
const manager = new CardElementManager(
|
||||
api,
|
||||
element,
|
||||
() => undefined,
|
||||
() => undefined,
|
||||
);
|
||||
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
new View({ view: 'diagnostics' }),
|
||||
);
|
||||
vi.mocked(api.getViewManager().canSetViewDefault).mockReturnValue(false);
|
||||
|
||||
const dialog = createDialogWithCard(element);
|
||||
document.body.append(dialog);
|
||||
manager.elementConnected();
|
||||
|
||||
fireFromDialog(dialog);
|
||||
|
||||
expect(api.getViewManager().reset).toHaveBeenCalled();
|
||||
expect(api.getViewManager().setViewDefault).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not set view to diagnostics if card is not in editor', () => {
|
||||
const api = createCardAPI();
|
||||
const element = createCardHTMLElement();
|
||||
|
||||
@@ -203,6 +203,26 @@ describe('should not set view without cameras being initialized', () => {
|
||||
|
||||
expect(manager.getView()).toBeNull();
|
||||
});
|
||||
|
||||
it('should report whether a default view can be set', () => {
|
||||
expect(new ViewManager(createInitializedCardAPI()).canSetViewDefault()).toBe(true);
|
||||
expect(new ViewManager(createInitializedCardAPI(false)).canSetViewDefault()).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('should set the diagnostics view without cameras being initialized', () => {
|
||||
const view = createView({ view: 'diagnostics' });
|
||||
const factory = mock<ViewFactory>();
|
||||
factory.getViewByParameters.mockReturnValue(view);
|
||||
|
||||
const manager = new ViewManager(createInitializedCardAPI(false), {
|
||||
viewFactory: factory,
|
||||
});
|
||||
manager.setViewByParameters({ params: { view: 'diagnostics' } });
|
||||
|
||||
expect(manager.getView()).toBe(view);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should respect microphone navigation lock', () => {
|
||||
@@ -494,6 +514,19 @@ describe('should handle exceptions', () => {
|
||||
expect(api.getIssueManager().reset).toHaveBeenCalledWith('view_incompatible');
|
||||
});
|
||||
|
||||
it('should retain issues when the diagnostics view is requested', () => {
|
||||
const viewFactory = mock<ViewFactory>();
|
||||
viewFactory.getViewByParameters.mockReturnValue(createView({ view: 'diagnostics' }));
|
||||
|
||||
const api = createInitializedCardAPI();
|
||||
const manager = new ViewManager(api, { viewFactory });
|
||||
manager.setViewByParameters({ params: { view: 'diagnostics' } });
|
||||
|
||||
expect(manager.getView()?.is('diagnostics')).toBeTruthy();
|
||||
expect(api.getIssueManager().reset).not.toHaveBeenCalledWith('view_incompatible');
|
||||
expect(api.getIssueManager().reset).not.toHaveBeenCalledWith('media_query');
|
||||
});
|
||||
|
||||
it('should return null when failSafe view factory also throws', () => {
|
||||
const viewFactory = mock<ViewFactory>();
|
||||
viewFactory.getViewDefault.mockImplementation(() => {
|
||||
|
||||
Reference in New Issue
Block a user