test: Improve timezone resilience / cleanliness of tests (#2279)
- Related: https://github.com/dermotduffy/advanced-camera-card/pull/2278 - Reported by @0x464e
This commit is contained in:
@@ -381,8 +381,9 @@ describe('ReolinkCameraManagerEngine', () => {
|
|||||||
{
|
{
|
||||||
_metadata: {
|
_metadata: {
|
||||||
cameraID: 'office',
|
cameraID: 'office',
|
||||||
startDate: new Date('2024-11-04T21:37:00.000Z'),
|
// Date parsed from directory '2024/11/4' + time '21:37:00' in local time
|
||||||
endDate: new Date('2024-11-04T21:37:00.000Z'),
|
startDate: new Date(2024, 10, 4, 21, 37, 0),
|
||||||
|
endDate: new Date(2024, 10, 4, 21, 37, 0),
|
||||||
},
|
},
|
||||||
can_expand: false,
|
can_expand: false,
|
||||||
can_play: true,
|
can_play: true,
|
||||||
@@ -397,8 +398,9 @@ describe('ReolinkCameraManagerEngine', () => {
|
|||||||
{
|
{
|
||||||
_metadata: {
|
_metadata: {
|
||||||
cameraID: 'office',
|
cameraID: 'office',
|
||||||
endDate: new Date('2024-11-04T21:35:46.000Z'),
|
// Date parsed from directory '2024/11/4' + time '21:35:05' with duration in local time
|
||||||
startDate: new Date('2024-11-04T21:35:05.000Z'),
|
endDate: new Date(2024, 10, 4, 21, 35, 46),
|
||||||
|
startDate: new Date(2024, 10, 4, 21, 35, 5),
|
||||||
what: ['person', 'vehicle', 'zebra'],
|
what: ['person', 'vehicle', 'zebra'],
|
||||||
},
|
},
|
||||||
can_expand: false,
|
can_expand: false,
|
||||||
@@ -414,8 +416,9 @@ describe('ReolinkCameraManagerEngine', () => {
|
|||||||
{
|
{
|
||||||
_metadata: {
|
_metadata: {
|
||||||
cameraID: 'office',
|
cameraID: 'office',
|
||||||
endDate: new Date('2024-11-04T21:29:46'),
|
// Date parsed from directory '2024/11/4' + time '21:29:05' with duration in local time
|
||||||
startDate: new Date('2024-11-04T21:29:05'),
|
endDate: new Date(2024, 10, 4, 21, 29, 46),
|
||||||
|
startDate: new Date(2024, 10, 4, 21, 29, 5),
|
||||||
},
|
},
|
||||||
can_expand: false,
|
can_expand: false,
|
||||||
can_play: true,
|
can_play: true,
|
||||||
@@ -430,8 +433,9 @@ describe('ReolinkCameraManagerEngine', () => {
|
|||||||
{
|
{
|
||||||
_metadata: {
|
_metadata: {
|
||||||
cameraID: 'office',
|
cameraID: 'office',
|
||||||
endDate: new Date('2024-11-04T21:24:27'),
|
// Date parsed from directory '2024/11/4' + time '21:23:53' with duration in local time
|
||||||
startDate: new Date('2024-11-04T21:23:53'),
|
endDate: new Date(2024, 10, 4, 21, 24, 27),
|
||||||
|
startDate: new Date(2024, 10, 4, 21, 23, 53),
|
||||||
},
|
},
|
||||||
can_expand: false,
|
can_expand: false,
|
||||||
can_play: true,
|
can_play: true,
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ describe('MetadataGenerator', () => {
|
|||||||
const browseMedia = createBrowseMedia({
|
const browseMedia = createBrowseMedia({
|
||||||
title: 'Test Media 2025-05-26 18:18',
|
title: 'Test Media 2025-05-26 18:18',
|
||||||
});
|
});
|
||||||
const expectedDate = new Date('2025-05-26T18:18:00.000Z');
|
// Create expected date using local time, since the date parser interprets
|
||||||
|
// the date string in local time (not UTC)
|
||||||
|
const expectedDate = new Date(2025, 4, 26, 18, 18);
|
||||||
const formatlessDateParser: Parser = {
|
const formatlessDateParser: Parser = {
|
||||||
type: 'startdate',
|
type: 'startdate',
|
||||||
};
|
};
|
||||||
@@ -129,10 +131,11 @@ describe('MetadataGenerator', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should incorporate parent metadata without a date format', async () => {
|
it('should incorporate parent metadata without a date format', async () => {
|
||||||
|
// Parent has a start date at local midnight on 2025-05-26
|
||||||
const parentBrowseMedia = createRichBrowseMedia({
|
const parentBrowseMedia = createRichBrowseMedia({
|
||||||
title: '2025-05-26',
|
title: '2025-05-26',
|
||||||
_metadata: {
|
_metadata: {
|
||||||
startDate: new Date('2025-05-26T00:00:00.000Z'),
|
startDate: new Date(2025, 4, 26, 0, 0),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const childBrowseMedia = createBrowseMedia({
|
const childBrowseMedia = createBrowseMedia({
|
||||||
@@ -145,7 +148,7 @@ describe('MetadataGenerator', () => {
|
|||||||
generator.generate(childBrowseMedia, parentBrowseMedia, [
|
generator.generate(childBrowseMedia, parentBrowseMedia, [
|
||||||
formatlessDateParser,
|
formatlessDateParser,
|
||||||
])?.startDate,
|
])?.startDate,
|
||||||
).toEqual(new Date('2025-05-26T22:42:00.000Z'));
|
).toEqual(new Date(2025, 4, 26, 22, 42));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -195,16 +198,19 @@ describe('MetadataGenerator', () => {
|
|||||||
const generator = new MetadataGenerator();
|
const generator = new MetadataGenerator();
|
||||||
await generator.prepare(parsers);
|
await generator.prepare(parsers);
|
||||||
|
|
||||||
|
// The parsed date combines 20250507 (May 7, 2025) and 171758 (17:17:58)
|
||||||
|
// interpreted in local time
|
||||||
expect(generator.generate(browseMedia, undefined, parsers)?.startDate).toEqual(
|
expect(generator.generate(browseMedia, undefined, parsers)?.startDate).toEqual(
|
||||||
new Date('2025-05-07T17:17:58.000Z'),
|
new Date(2025, 4, 7, 17, 17, 58),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should incorporate parent metadata with a date format', async () => {
|
it('should incorporate parent metadata with a date format', async () => {
|
||||||
|
// Parent has a start date at local midnight on 2025-05-26
|
||||||
const parentBrowseMedia = createRichBrowseMedia({
|
const parentBrowseMedia = createRichBrowseMedia({
|
||||||
title: '2025-05-26',
|
title: '2025-05-26',
|
||||||
_metadata: {
|
_metadata: {
|
||||||
startDate: new Date('2025-05-26T00:00:00.000Z'),
|
startDate: new Date(2025, 4, 26, 0, 0),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const childBrowseMedia = createBrowseMedia({
|
const childBrowseMedia = createBrowseMedia({
|
||||||
@@ -219,7 +225,7 @@ describe('MetadataGenerator', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
generator.generate(childBrowseMedia, parentBrowseMedia, [parser])?.startDate,
|
generator.generate(childBrowseMedia, parentBrowseMedia, [parser])?.startDate,
|
||||||
).toEqual(new Date('2025-05-26T22:42:00.000Z'));
|
).toEqual(new Date(2025, 4, 26, 22, 42));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { format } from 'date-fns';
|
||||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
import { ViewItemManager } from '../../../src/card-controller/view/item-manager';
|
import { ViewItemManager } from '../../../src/card-controller/view/item-manager';
|
||||||
import { homeAssistantSignPath } from '../../../src/ha/sign-path.js';
|
import { homeAssistantSignPath } from '../../../src/ha/sign-path.js';
|
||||||
@@ -189,7 +190,8 @@ describe('ViewItemManager', () => {
|
|||||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||||
|
|
||||||
const manager = new ViewItemManager(api);
|
const manager = new ViewItemManager(api);
|
||||||
const item = new TestViewMedia({ startTime: new Date('2025-05-03T17:41:00Z') });
|
const startTime = new Date('2025-05-03T17:41:00Z');
|
||||||
|
const item = new TestViewMedia({ startTime });
|
||||||
|
|
||||||
vi.mocked(api.getCameraManager().getMediaDownloadPath).mockResolvedValue({
|
vi.mocked(api.getCameraManager().getMediaDownloadPath).mockResolvedValue({
|
||||||
sign: false,
|
sign: false,
|
||||||
@@ -197,7 +199,10 @@ describe('ViewItemManager', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(await manager.download(item)).toBe(true);
|
expect(await manager.download(item)).toBe(true);
|
||||||
expect(downloadURL).toBeCalledWith('foo', 'camera_id_2025-05-03-17-41-00.mp4');
|
|
||||||
|
// 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).toBeCalledWith('foo', expectedFilename);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate filename for snapshot', async () => {
|
it('should generate filename for snapshot', async () => {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
import { format } from 'date-fns';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { mock } from 'vitest-mock-extended';
|
import { mock } from 'vitest-mock-extended';
|
||||||
import { CameraManager } from '../../../src/camera-manager/manager';
|
import { CameraManager } from '../../../src/camera-manager/manager';
|
||||||
import { ThumbnailDetailsController } from '../../../src/components-lib/thumbnail/details-controller';
|
import { ThumbnailDetailsController } from '../../../src/components-lib/thumbnail/details-controller';
|
||||||
|
import { formatDateAndTime } from '../../../src/utils/basic';
|
||||||
import { ViewFolder, ViewMediaType } from '../../../src/view/item';
|
import { ViewFolder, ViewMediaType } from '../../../src/view/item';
|
||||||
import { createFolder, TestViewMedia } from '../../test-utils';
|
import { createFolder, TestViewMedia } from '../../test-utils';
|
||||||
|
|
||||||
@@ -135,14 +137,17 @@ describe('ThumbnailDetailsController', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should have start time in details', () => {
|
it('should have start time in details', () => {
|
||||||
|
const startTime = new Date('2025-05-18T17:03:00Z');
|
||||||
const item = new TestViewMedia({
|
const item = new TestViewMedia({
|
||||||
startTime: new Date('2025-05-18T17:03:00Z'),
|
startTime,
|
||||||
});
|
});
|
||||||
|
|
||||||
const controller = new ThumbnailDetailsController();
|
const controller = new ThumbnailDetailsController();
|
||||||
controller.calculate(null, item);
|
controller.calculate(null, item);
|
||||||
|
|
||||||
|
// Use formatDateAndTime to generate expected value (formats in local time with seconds)
|
||||||
expect(controller.getDetails()).toContainEqual({
|
expect(controller.getDetails()).toContainEqual({
|
||||||
title: '2025-05-18 17:03:00',
|
title: formatDateAndTime(startTime, true),
|
||||||
hint: 'Start',
|
hint: 'Start',
|
||||||
icon: { icon: 'mdi:calendar-clock-outline' },
|
icon: { icon: 'mdi:calendar-clock-outline' },
|
||||||
});
|
});
|
||||||
@@ -249,11 +254,14 @@ describe('ThumbnailDetailsController', () => {
|
|||||||
|
|
||||||
it('should have seek in details', () => {
|
it('should have seek in details', () => {
|
||||||
const item = new TestViewMedia();
|
const item = new TestViewMedia();
|
||||||
|
const seekTime = new Date('2025-05-20T07:14:57Z');
|
||||||
|
|
||||||
const controller = new ThumbnailDetailsController();
|
const controller = new ThumbnailDetailsController();
|
||||||
controller.calculate(null, item, new Date('2025-05-20T07:14:57Z'));
|
controller.calculate(null, item, seekTime);
|
||||||
|
|
||||||
|
// Use format() to generate expected value (formats in local time)
|
||||||
expect(controller.getDetails()).toContainEqual({
|
expect(controller.getDetails()).toContainEqual({
|
||||||
title: '07:14:57',
|
title: format(seekTime, 'HH:mm:ss'),
|
||||||
hint: 'Seek',
|
hint: 'Seek',
|
||||||
icon: { icon: 'mdi:clock-fast' },
|
icon: { icon: 'mdi:clock-fast' },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { format } from 'date-fns';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { mock } from 'vitest-mock-extended';
|
import { mock } from 'vitest-mock-extended';
|
||||||
import { CameraManager } from '../../../../src/camera-manager/manager';
|
import { CameraManager } from '../../../../src/camera-manager/manager';
|
||||||
@@ -18,7 +19,9 @@ describe('ThumbnailFeatureController', () => {
|
|||||||
|
|
||||||
controller.calculate(null, itemWithTime, false);
|
controller.calculate(null, itemWithTime, false);
|
||||||
|
|
||||||
expect(controller.getTitle()).toBe('17:03');
|
// Use format() to generate expected time in local timezone
|
||||||
|
const expectedTime = format(itemWithTime.getStartTime()!, 'HH:mm');
|
||||||
|
expect(controller.getTitle()).toBe(expectedTime);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not set title when details are shown ', () => {
|
it('should not set title when details are shown ', () => {
|
||||||
@@ -50,7 +53,9 @@ describe('ThumbnailFeatureController', () => {
|
|||||||
|
|
||||||
controller.calculate(null, itemWithTime, false);
|
controller.calculate(null, itemWithTime, false);
|
||||||
|
|
||||||
expect(controller.getSubtitles()).toContain('May 18th');
|
// Use format() to generate expected date string (formats in local time)
|
||||||
|
const expectedDate = format(itemWithTime.getStartTime()!, 'MMM do');
|
||||||
|
expect(controller.getSubtitles()).toContain(expectedDate);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set subtitle with source from item title ', () => {
|
it('should set subtitle with source from item title ', () => {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
import { format } from 'date-fns';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import {
|
import {
|
||||||
BrowseMediaEventViewMedia,
|
BrowseMediaEventViewMedia,
|
||||||
BrowseMediaViewFolder,
|
BrowseMediaViewFolder,
|
||||||
} from '../../../src/ha/browse-media/item';
|
} from '../../../src/ha/browse-media/item';
|
||||||
|
import { formatDateAndTime } from '../../../src/utils/basic';
|
||||||
import { VideoContentType, ViewMediaType } from '../../../src/view/item';
|
import { VideoContentType, ViewMediaType } from '../../../src/view/item';
|
||||||
import {
|
import {
|
||||||
createBrowseMedia,
|
createBrowseMedia,
|
||||||
@@ -45,17 +47,21 @@ describe('BrowseMediaEventViewMedia', () => {
|
|||||||
|
|
||||||
describe('should set ID', () => {
|
describe('should set ID', () => {
|
||||||
it('should set id from metadata', () => {
|
it('should set id from metadata', () => {
|
||||||
|
const startDate = new Date('2025-05-05T07:46:00Z');
|
||||||
const browseMedia = createRichBrowseMedia({
|
const browseMedia = createRichBrowseMedia({
|
||||||
media_content_id: 'media_content_id',
|
media_content_id: 'media_content_id',
|
||||||
_metadata: {
|
_metadata: {
|
||||||
startDate: new Date('2025-05-05T07:46:00Z'),
|
startDate,
|
||||||
endDate: new Date('2025-05-05T07:48:00Z'),
|
endDate: new Date('2025-05-05T07:48:00Z'),
|
||||||
cameraID: 'camera.office',
|
cameraID: 'camera.office',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const viewMedia = new BrowseMediaEventViewMedia(ViewMediaType.Clip, browseMedia);
|
const viewMedia = new BrowseMediaEventViewMedia(ViewMediaType.Clip, browseMedia);
|
||||||
expect(viewMedia.getID()).toBe('camera.office/2025-05-05 07:46:00');
|
|
||||||
|
// The ID is formatted using local time, so we use format() to generate expected value
|
||||||
|
const expectedId = `camera.office/${format(startDate, 'yyyy-MM-dd HH:mm:ss')}`;
|
||||||
|
expect(viewMedia.getID()).toBe(expectedId);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set id from media_content_id from the options', () => {
|
it('should set id from media_content_id from the options', () => {
|
||||||
@@ -125,16 +131,19 @@ describe('BrowseMediaEventViewMedia', () => {
|
|||||||
|
|
||||||
describe('should get title', () => {
|
describe('should get title', () => {
|
||||||
it('should get title from metadata start time', () => {
|
it('should get title from metadata start time', () => {
|
||||||
|
const startDate = new Date('2025-05-05T07:46:00Z');
|
||||||
const browseMedia = createRichBrowseMedia({
|
const browseMedia = createRichBrowseMedia({
|
||||||
_metadata: {
|
_metadata: {
|
||||||
startDate: new Date('2025-05-05T07:46:00Z'),
|
startDate: startDate,
|
||||||
endDate: new Date('2025-05-05T07:48:00Z'),
|
endDate: new Date('2025-05-05T07:48:00Z'),
|
||||||
cameraID: 'camera.office',
|
cameraID: 'camera.office',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const viewMedia = new BrowseMediaEventViewMedia(ViewMediaType.Clip, browseMedia);
|
const viewMedia = new BrowseMediaEventViewMedia(ViewMediaType.Clip, browseMedia);
|
||||||
expect(viewMedia.getTitle()).toEqual('2025-05-05 07:46');
|
|
||||||
|
// The title is formatted using local time via formatDateAndTime
|
||||||
|
expect(viewMedia.getTitle()).toEqual(formatDateAndTime(startDate));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should get title without metadata', () => {
|
it('should get title without metadata', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user