fix: Should recover from an interrupted initialization (#2673)
This commit is contained in:
@@ -6,7 +6,7 @@ import { loadLanguages } from '../../localize/localize';
|
|||||||
import { errorToConsole } from '../../utils/basic';
|
import { errorToConsole } from '../../utils/basic';
|
||||||
import { Initializer } from '../../utils/initializer/initializer';
|
import { Initializer } from '../../utils/initializer/initializer';
|
||||||
import type { CardInitializerAPI } from '../types';
|
import type { CardInitializerAPI } from '../types';
|
||||||
import { SessionManager } from './session-manager';
|
import { SessionManager, SessionState } from './session-manager';
|
||||||
|
|
||||||
export enum InitializationAspect {
|
export enum InitializationAspect {
|
||||||
LANGUAGES = 'languages',
|
LANGUAGES = 'languages',
|
||||||
@@ -90,11 +90,9 @@ export class InitializationManager {
|
|||||||
// HASSManager); a reconnect or a cleared issue reaches it by causing a
|
// HASSManager); a reconnect or a cleared issue reaches it by causing a
|
||||||
// render.
|
// render.
|
||||||
//
|
//
|
||||||
// The check here is only to keep cost down: a card that has finished
|
// The check here is a filter rather than the decision: a card that has
|
||||||
// initializing re-renders often, and without it each of those renders would
|
// finished initializing re-renders often, and without it each of those
|
||||||
// queue an attempt that does nothing. `_initializeMandatory()` checks the
|
// renders would queue an attempt that does nothing.
|
||||||
// same conditions again when it actually runs, and that is the one that
|
|
||||||
// matters for correctness.
|
|
||||||
public triggerInitialization(): void {
|
public triggerInitialization(): void {
|
||||||
if (!this._shouldInitializeMandatory()) {
|
if (!this._shouldInitializeMandatory()) {
|
||||||
return;
|
return;
|
||||||
@@ -107,7 +105,15 @@ export class InitializationManager {
|
|||||||
this._api.getConfigManager().hasConfig() &&
|
this._api.getConfigManager().hasConfig() &&
|
||||||
this._api.getCardElementManager().isConnected() &&
|
this._api.getCardElementManager().isConnected() &&
|
||||||
isHassReady(this._api.getHASSManager().getHASS()) &&
|
isHassReady(this._api.getHASSManager().getHASS()) &&
|
||||||
!this.areMandatoryAspectsInitialized() &&
|
// Start when aspects remain to be initialized, or when the session is
|
||||||
|
// idle even though aspects are otherwise initialized. A run that
|
||||||
|
// initialized every aspect and then declined (e.g. because something
|
||||||
|
// ended its session), reports no outcome at all, so only a later run can
|
||||||
|
// report the card as actually started, and that run will find nothing
|
||||||
|
// left to initialize.
|
||||||
|
// See: https://github.com/dermotduffy/advanced-camera-card/issues/2672
|
||||||
|
(!this.areMandatoryAspectsInitialized() ||
|
||||||
|
this._sessionManager.getState() === SessionState.IDLE) &&
|
||||||
// Don't start while a full-card issue (e.g. the "Home Assistant is
|
// Don't start while a full-card issue (e.g. the "Home Assistant is
|
||||||
// starting" notice) is shown: each initialization step aborts as soon as
|
// starting" notice) is shown: each initialization step aborts as soon as
|
||||||
// it sees one, so an attempt now would be wasted. The card tries again
|
// it sees one, so an attempt now would be wasted. The card tries again
|
||||||
|
|||||||
@@ -214,6 +214,42 @@ describe('InitializationManager', () => {
|
|||||||
expect(manager.getSessionManager().wasEverInitialized()).toBeFalsy();
|
expect(manager.getSessionManager().wasEverInitialized()).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should start the card after a run that initialized every aspect declined to report an outcome', async () => {
|
||||||
|
// See: https://github.com/dermotduffy/advanced-camera-card/issues/2672
|
||||||
|
const api = createReadyAPI();
|
||||||
|
const stateManager = new ConditionStateManager();
|
||||||
|
vi.mocked(api.getConditionStateManager).mockReturnValue(stateManager);
|
||||||
|
|
||||||
|
const manager = new InitializationManager(api);
|
||||||
|
|
||||||
|
// Home Assistant stops being ready midway through, which ends the session
|
||||||
|
// without invalidating any aspect. The run carries on to initialize
|
||||||
|
// everything, then declines because its session is gone.
|
||||||
|
vi.mocked(api.getViewManager().initialize).mockImplementation(async () => {
|
||||||
|
manager.getSessionManager().end();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
await manager.initializeMandatory();
|
||||||
|
|
||||||
|
expect(manager.areMandatoryAspectsInitialized()).toBeTruthy();
|
||||||
|
expect(manager.getSessionManager().wasEverInitialized()).toBeFalsy();
|
||||||
|
|
||||||
|
// A render or a hass change reaches the card afterwards.
|
||||||
|
manager.triggerInitialization();
|
||||||
|
|
||||||
|
await vi.waitFor(() =>
|
||||||
|
expect(manager.getSessionManager().wasEverInitialized()).toBeTruthy(),
|
||||||
|
);
|
||||||
|
expect(stateManager.getState().initialized).toBe(true);
|
||||||
|
expect(api.getAutomationsManager().subscribe).toHaveBeenCalled();
|
||||||
|
|
||||||
|
// The later run found every aspect already initialized.
|
||||||
|
expect(api.getCameraManager().initializeCamerasFromConfig).toHaveBeenCalledTimes(
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('should stop when a full-card issue appears during initialization', async () => {
|
it('should stop when a full-card issue appears during initialization', async () => {
|
||||||
const api = createReadyAPI();
|
const api = createReadyAPI();
|
||||||
|
|
||||||
@@ -682,10 +718,15 @@ describe('InitializationManager', () => {
|
|||||||
expect(initializer.initializeMultipleIfNecessary).not.toHaveBeenCalled();
|
expect(initializer.initializeMultipleIfNecessary).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not initialize when already initialized', () => {
|
it('should not initialize when already initialized and the session is running', () => {
|
||||||
const initializer = mock<Initializer>();
|
const initializer = mock<Initializer>();
|
||||||
initializer.isInitializedMultiple.mockReturnValue(true);
|
initializer.isInitializedMultiple.mockReturnValue(true);
|
||||||
const manager = new InitializationManager(createReadyAPI(), initializer);
|
const manager = new InitializationManager(createReadyAPI(), initializer);
|
||||||
|
const sessionManager = manager.getSessionManager();
|
||||||
|
sessionManager.reportInitializationSucceeded(
|
||||||
|
sessionManager.startInitialization(),
|
||||||
|
createConfig(),
|
||||||
|
);
|
||||||
|
|
||||||
manager.triggerInitialization();
|
manager.triggerInitialization();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user