feat: Add proxying support for images (#2427)

- Closes #2418
This commit is contained in:
Dermot Duffy
2026-06-30 17:45:12 -07:00
committed by dermotduffy
parent 9384785d37
commit 1cd5520154
51 changed files with 2404 additions and 687 deletions
@@ -9,25 +9,50 @@ describe('CachedValueController', () => {
vi.useRealTimers();
});
it('should not restart timer on hostUpdate if not connected', () => {
const host = mock<ReactiveControllerHost & HTMLElement>();
const callback = vi.fn().mockReturnValue(42);
let refreshSeconds: number | null = 10;
const controller = new CachedValueController(host, () => refreshSeconds, callback);
Object.defineProperty(host, 'isConnected', { get: () => false });
refreshSeconds = 20;
controller.hostUpdate();
expect(controller.hasTimer()).toBeFalsy();
});
it('should not restart timer if unchanged on hostUpdate', () => {
const host = mock<ReactiveControllerHost & HTMLElement>();
const callback = vi.fn().mockReturnValue(42);
const refreshSeconds: number | null = 10;
const controller = new CachedValueController(host, () => refreshSeconds, callback);
Object.defineProperty(host, 'isConnected', { get: () => true });
// This starts the timer as isConnected is true
controller.hostConnected();
const startTimerSpy = vi.spyOn(controller, 'startTimer');
controller.hostUpdate();
// Should not restart since refreshSeconds hasn't changed
expect(startTimerSpy).not.toBeCalled();
});
it('should construct', () => {
const host = mock<ReactiveControllerHost>();
const host = mock<ReactiveControllerHost & HTMLElement>();
const callback = vi.fn();
const controller = new CachedValueController(host, 10, callback);
const controller = new CachedValueController(host, () => 10, callback);
expect(controller).toBeTruthy();
});
it('should remove host', () => {
const host = mock<ReactiveControllerHost>();
const callback = vi.fn();
const controller = new CachedValueController(host, 10, callback);
controller.removeController();
expect(host.removeController).toBeCalled();
});
it('should have timer', () => {
const host = mock<ReactiveControllerHost>();
const host = mock<ReactiveControllerHost & HTMLElement>();
const callback = vi.fn();
const startCallback = vi.fn();
const stopCallback = vi.fn();
@@ -36,7 +61,7 @@ describe('CachedValueController', () => {
const controller = new CachedValueController(
host,
10,
() => 10,
callback,
startCallback,
stopCallback,
@@ -49,13 +74,13 @@ describe('CachedValueController', () => {
vi.runOnlyPendingTimers();
expect(callback).toBeCalled();
expect(host.requestUpdate).toBeCalled();
expect(controller.value).toBe(3);
expect(controller.getValue()).toBe(3);
callback.mockReturnValue(4);
vi.runOnlyPendingTimers();
expect(callback).toBeCalled();
expect(host.requestUpdate).toBeCalled();
expect(controller.value).toBe(4);
expect(controller.getValue()).toBe(4);
expect(controller.hasTimer()).toBeTruthy();
@@ -68,42 +93,129 @@ describe('CachedValueController', () => {
});
it('should clear value', () => {
const host = mock<ReactiveControllerHost>();
const host = mock<ReactiveControllerHost & HTMLElement>();
const callback = vi.fn().mockReturnValue(42);
vi.useFakeTimers();
const controller = new CachedValueController(host, 10, callback);
const controller = new CachedValueController(host, () => 10, callback);
controller.startTimer();
vi.runOnlyPendingTimers();
expect(controller.value).equal(42);
expect(controller.getValue()).equal(42);
controller.clearValue();
expect(controller.value).toBeUndefined();
expect(controller.getValue()).toBeNull();
});
it('should connect and disconnect host', () => {
const host = mock<ReactiveControllerHost>();
const host = mock<ReactiveControllerHost & HTMLElement>();
const callback = vi.fn().mockReturnValue(43);
const startCallback = vi.fn();
const stopCallback = vi.fn();
const controller = new CachedValueController(
host,
10,
() => 10,
callback,
startCallback,
stopCallback,
);
controller.hostConnected();
expect(controller.value).equal(43);
expect(controller.getValue()).equal(43);
expect(startCallback).toBeCalled();
expect(host.requestUpdate).toBeCalled();
controller.hostDisconnected();
expect(controller.value).toBeUndefined();
expect(controller.getValue()).toBeNull();
expect(stopCallback).toBeCalled();
});
it('should call timer tick callback on each tick before updateValue', () => {
const host = mock<ReactiveControllerHost & HTMLElement>();
const callback = vi.fn().mockReturnValue('value');
const tickCallback = vi.fn();
vi.useFakeTimers();
const controller = new CachedValueController(
host,
() => 5,
callback,
undefined,
undefined,
tickCallback,
);
controller.startTimer();
vi.runOnlyPendingTimers();
expect(tickCallback).toHaveBeenCalledTimes(1);
expect(callback).toHaveBeenCalledTimes(1);
vi.runOnlyPendingTimers();
expect(tickCallback).toHaveBeenCalledTimes(2);
expect(callback).toHaveBeenCalledTimes(2);
});
it('should not call timerTickCallback on manual updateValue', () => {
const host = mock<ReactiveControllerHost & HTMLElement>();
const callback = vi.fn().mockReturnValue('value');
const tickCallback = vi.fn();
const controller = new CachedValueController(
host,
() => 5,
callback,
undefined,
undefined,
tickCallback,
);
controller.updateValue();
expect(callback).toHaveBeenCalledTimes(1);
expect(tickCallback).not.toHaveBeenCalled();
});
it('should restart timer with new interval on hostUpdate', () => {
const host = mock<ReactiveControllerHost & HTMLElement>();
const callback = vi.fn().mockReturnValue(42);
let refreshSeconds: number | null = null;
const controller = new CachedValueController(host, () => refreshSeconds, callback);
vi.useFakeTimers();
Object.defineProperty(host, 'isConnected', { get: () => true });
controller.hostConnected();
expect(controller.hasTimer()).toBeFalsy();
refreshSeconds = 20;
controller.hostUpdate();
// Timer should have been restarted. Fast forward 15 seconds. If it didn't
// restart, it would fire at 10 seconds. Since it restarted at 20 seconds,
// it shouldn't fire at 15 seconds.
callback.mockClear();
vi.advanceTimersByTime(15 * 1000);
expect(callback).not.toBeCalled();
vi.advanceTimersByTime(5 * 1000);
expect(callback).toBeCalled();
// Now set it to null -> stops timer
refreshSeconds = null;
controller.hostUpdate();
expect(controller.hasTimer()).toBeFalsy();
// Now set it to 0 -> stops timer
refreshSeconds = 0;
controller.hostUpdate();
expect(controller.hasTimer()).toBeFalsy();
// Now set it to negative -> stops timer
refreshSeconds = -1;
controller.hostUpdate();
expect(controller.hasTimer()).toBeFalsy();
});
});
@@ -144,7 +144,7 @@ describe('UpdatingImageMediaPlayerController', () => {
it('should return screenshot URL with cached value controller', async () => {
const url = 'data:image/png;base64,';
const cachedValueController = mock<CachedValueController<string>>();
Object.defineProperty(cachedValueController, 'value', { value: url });
cachedValueController.getValue.mockReturnValue(url);
const controller = new UpdatingImageMediaPlayerController(
createLitElement(),
File diff suppressed because it is too large Load Diff