335 lines
11 KiB
TypeScript
335 lines
11 KiB
TypeScript
import { format } from 'date-fns';
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import { ViewItemManager } from '../../../src/card-controller/view/item-manager';
|
|
import { homeAssistantGetSignedURLIfNecessary } from '../../../src/ha/sign-path.js';
|
|
import { downloadURL } from '../../../src/utils/download';
|
|
import { ViewFolder, ViewMediaType } from '../../../src/view/item';
|
|
import { createCardAPI, createFolder, createHASS } from '../../test-utils';
|
|
import { TestViewMedia } from '../../view/test-utils';
|
|
|
|
vi.mock('../../../src/utils/download');
|
|
vi.mock('../../../src/ha/sign-path.js');
|
|
|
|
describe('ViewItemManager', () => {
|
|
describe('getCapabilities', () => {
|
|
it('should return null for unsupported item types', () => {
|
|
const api = createCardAPI();
|
|
const manager = new ViewItemManager(api);
|
|
|
|
expect(manager.getCapabilities(new TestViewMedia({ cameraID: null }))).toBeNull();
|
|
});
|
|
|
|
it('should return capabilities for camera media', () => {
|
|
const api = createCardAPI();
|
|
const capabilities = {
|
|
canDownload: true,
|
|
canFavorite: true,
|
|
};
|
|
vi.mocked(api.getCameraManager().getMediaCapabilities).mockReturnValue(
|
|
capabilities,
|
|
);
|
|
|
|
const manager = new ViewItemManager(api);
|
|
const item = new TestViewMedia({ cameraID: 'camera.office' });
|
|
|
|
expect(manager.getCapabilities(item)).toEqual(capabilities);
|
|
});
|
|
|
|
it('should return capabilities for folder media', () => {
|
|
const api = createCardAPI();
|
|
const capabilities = {
|
|
canDownload: true,
|
|
canFavorite: true,
|
|
};
|
|
vi.mocked(api.getFoldersManager().getItemCapabilities).mockReturnValue(
|
|
capabilities,
|
|
);
|
|
|
|
const manager = new ViewItemManager(api);
|
|
const item = new TestViewMedia({ folder: createFolder() });
|
|
|
|
expect(manager.getCapabilities(item)).toEqual(capabilities);
|
|
});
|
|
});
|
|
|
|
describe('download', () => {
|
|
beforeEach(() => {
|
|
vi.mocked(homeAssistantGetSignedURLIfNecessary).mockImplementation(
|
|
async (_hass, endpoint) => endpoint.endpoint,
|
|
);
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it('should not download without hass', async () => {
|
|
const api = createCardAPI();
|
|
const manager = new ViewItemManager(api);
|
|
const item = new TestViewMedia({ cameraID: 'camera.office' });
|
|
|
|
expect(await manager.download(item)).toBe(true);
|
|
expect(downloadURL).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should throw error when signing fails', async () => {
|
|
const consoleSpy = vi.spyOn(global.console, 'warn').mockReturnValue(undefined);
|
|
|
|
const api = createCardAPI();
|
|
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
|
|
|
const manager = new ViewItemManager(api);
|
|
const item = new TestViewMedia({ cameraID: 'camera.office' });
|
|
|
|
vi.mocked(api.getCameraManager().getMediaDownloadPath).mockResolvedValue({
|
|
sign: true,
|
|
endpoint: 'foo',
|
|
});
|
|
|
|
const signError = new Error('sign-error');
|
|
vi.mocked(homeAssistantGetSignedURLIfNecessary).mockRejectedValue(signError);
|
|
|
|
expect(await manager.download(item)).toBe(false);
|
|
expect(api.getNotificationManager().setNotification).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
heading: expect.objectContaining({
|
|
text: 'Download failed',
|
|
icon: 'mdi:download-off',
|
|
}),
|
|
body: expect.objectContaining({
|
|
text: 'Could not sign media URL for download',
|
|
}),
|
|
}),
|
|
);
|
|
expect(consoleSpy).toHaveBeenCalledWith('sign-error');
|
|
});
|
|
|
|
it('should download camera media', async () => {
|
|
const api = createCardAPI();
|
|
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
|
|
|
const manager = new ViewItemManager(api);
|
|
const item = new TestViewMedia({ cameraID: 'camera.office' });
|
|
|
|
vi.mocked(api.getCameraManager().getMediaDownloadPath).mockResolvedValue({
|
|
sign: true,
|
|
endpoint: 'foo',
|
|
});
|
|
|
|
vi.mocked(homeAssistantGetSignedURLIfNecessary).mockResolvedValue(
|
|
'http://foo/signed-url',
|
|
);
|
|
|
|
expect(await manager.download(item)).toBe(true);
|
|
expect(downloadURL).toHaveBeenCalledWith(
|
|
'http://foo/signed-url',
|
|
'camera-office_id.mp4',
|
|
);
|
|
});
|
|
|
|
it('should download folder media', async () => {
|
|
const api = createCardAPI();
|
|
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
|
|
|
const manager = new ViewItemManager(api);
|
|
const item = new TestViewMedia({ cameraID: null, folder: createFolder() });
|
|
|
|
vi.mocked(api.getFoldersManager().getDownloadPath).mockResolvedValue({
|
|
sign: true,
|
|
endpoint: 'foo',
|
|
});
|
|
|
|
vi.mocked(homeAssistantGetSignedURLIfNecessary).mockResolvedValue(
|
|
'http://foo/signed-url',
|
|
);
|
|
|
|
expect(await manager.download(item)).toBe(true);
|
|
expect(downloadURL).toHaveBeenCalledWith('http://foo/signed-url', 'media_id.mp4');
|
|
});
|
|
|
|
it('should download media without signing', async () => {
|
|
const api = createCardAPI();
|
|
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
|
|
|
const manager = new ViewItemManager(api);
|
|
const item = new TestViewMedia({ cameraID: 'camera.office' });
|
|
|
|
vi.mocked(api.getCameraManager().getMediaDownloadPath).mockResolvedValue({
|
|
sign: false,
|
|
endpoint: 'foo',
|
|
});
|
|
|
|
expect(await manager.download(item)).toBe(true);
|
|
expect(downloadURL).toHaveBeenCalledWith('foo', 'camera-office_id.mp4');
|
|
});
|
|
|
|
it('should download media without camera or folder', async () => {
|
|
const api = createCardAPI();
|
|
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
|
|
|
const manager = new ViewItemManager(api);
|
|
const item = new TestViewMedia({ cameraID: null, folder: null });
|
|
|
|
expect(await manager.download(item)).toBe(false);
|
|
expect(api.getNotificationManager().setNotification).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
heading: expect.objectContaining({
|
|
text: 'Download failed',
|
|
icon: 'mdi:download-off',
|
|
}),
|
|
body: expect.objectContaining({
|
|
text: 'No media to download',
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
|
|
describe('should generate useful download filenames', () => {
|
|
it('should generate filename for media without ID', async () => {
|
|
const api = createCardAPI();
|
|
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
|
|
|
const manager = new ViewItemManager(api);
|
|
const item = new TestViewMedia({ id: null });
|
|
|
|
vi.mocked(api.getCameraManager().getMediaDownloadPath).mockResolvedValue({
|
|
sign: false,
|
|
endpoint: 'foo',
|
|
});
|
|
|
|
expect(await manager.download(item)).toBe(true);
|
|
expect(downloadURL).toHaveBeenCalledWith('foo', 'camera.mp4');
|
|
});
|
|
|
|
it('should generate filename for media with start time', async () => {
|
|
const api = createCardAPI();
|
|
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
|
|
|
const manager = new ViewItemManager(api);
|
|
const startTime = new Date('2025-05-03T17:41:00Z');
|
|
const item = new TestViewMedia({ startTime });
|
|
|
|
vi.mocked(api.getCameraManager().getMediaDownloadPath).mockResolvedValue({
|
|
sign: false,
|
|
endpoint: 'foo',
|
|
});
|
|
|
|
expect(await manager.download(item)).toBe(true);
|
|
|
|
// Use format() to generate expected filename timestamp (formats in local time)
|
|
const expectedFilename = `camera_id_${format(startTime, 'yyyy-MM-dd-HH-mm-ss')}.mp4`;
|
|
expect(downloadURL).toHaveBeenCalledWith('foo', expectedFilename);
|
|
});
|
|
|
|
it('should generate filename for snapshot', async () => {
|
|
const api = createCardAPI();
|
|
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
|
|
|
const manager = new ViewItemManager(api);
|
|
const item = new TestViewMedia({ mediaType: ViewMediaType.Snapshot });
|
|
|
|
vi.mocked(api.getCameraManager().getMediaDownloadPath).mockResolvedValue({
|
|
sign: false,
|
|
endpoint: 'foo',
|
|
});
|
|
|
|
expect(await manager.download(item)).toBe(true);
|
|
expect(downloadURL).toHaveBeenCalledWith('foo', 'camera_id.jpg');
|
|
});
|
|
|
|
it('should generate filename for folder without title', async () => {
|
|
const api = createCardAPI();
|
|
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
|
|
|
const manager = new ViewItemManager(api);
|
|
const item = new ViewFolder(createFolder(), []);
|
|
|
|
vi.mocked(api.getFoldersManager().getDownloadPath).mockResolvedValue({
|
|
sign: false,
|
|
endpoint: 'foo',
|
|
});
|
|
|
|
expect(await manager.download(item)).toBe(true);
|
|
expect(downloadURL).toHaveBeenCalledWith('foo', 'media');
|
|
});
|
|
|
|
it('should generate filename for folder with title', async () => {
|
|
const api = createCardAPI();
|
|
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
|
|
|
const manager = new ViewItemManager(api);
|
|
const item = new ViewFolder(createFolder(), [], { title: 'title' });
|
|
|
|
vi.mocked(api.getFoldersManager().getDownloadPath).mockResolvedValue({
|
|
sign: false,
|
|
endpoint: 'foo',
|
|
});
|
|
|
|
expect(await manager.download(item)).toBe(true);
|
|
expect(downloadURL).toHaveBeenCalledWith('foo', 'title');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('favorite', () => {
|
|
it('should favorite camera media', async () => {
|
|
const api = createCardAPI();
|
|
|
|
const manager = new ViewItemManager(api);
|
|
const item = new TestViewMedia({
|
|
cameraID: 'camera.office',
|
|
mediaType: ViewMediaType.Snapshot,
|
|
});
|
|
|
|
await manager.favorite(item, true);
|
|
|
|
expect(api.getCameraManager().favoriteMedia).toHaveBeenCalledWith(item, true);
|
|
});
|
|
|
|
it('should favorite folder media', async () => {
|
|
const api = createCardAPI();
|
|
|
|
const manager = new ViewItemManager(api);
|
|
const item = new TestViewMedia({
|
|
folder: createFolder(),
|
|
mediaType: ViewMediaType.Snapshot,
|
|
});
|
|
|
|
await manager.favorite(item, true);
|
|
|
|
expect(api.getFoldersManager().favorite).toHaveBeenCalledWith(item, true);
|
|
});
|
|
});
|
|
|
|
describe('reviewMedia', () => {
|
|
it('should review camera media', async () => {
|
|
const api = createCardAPI();
|
|
|
|
const manager = new ViewItemManager(api);
|
|
const item = new TestViewMedia({
|
|
cameraID: 'camera.office',
|
|
mediaType: 'review' as ViewMediaType,
|
|
});
|
|
|
|
await manager.reviewMedia(item, true);
|
|
|
|
expect(api.getCameraManager().reviewMedia).toHaveBeenCalledWith(item, true);
|
|
});
|
|
|
|
it('should not review non-review media', async () => {
|
|
const api = createCardAPI();
|
|
|
|
const manager = new ViewItemManager(api);
|
|
const item = new TestViewMedia({
|
|
cameraID: 'camera.office',
|
|
mediaType: ViewMediaType.Clip,
|
|
});
|
|
|
|
await manager.reviewMedia(item, true);
|
|
|
|
expect(api.getCameraManager().reviewMedia).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|