feat: drive signs from a shared animation clock
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { createSSRApp, defineComponent, h, nextTick } from 'vue';
|
||||
import { createSSRApp, defineComponent, h, nextTick, shallowRef } from 'vue';
|
||||
import { renderToString } from '@vue/server-renderer';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { createScrollDriveController } from '@/journey/scroll-drive-controller';
|
||||
@@ -170,7 +170,7 @@ describe('useScrollDrive', () => {
|
||||
const app = createSSRApp(defineComponent({
|
||||
setup() {
|
||||
const drive = useScrollDrive();
|
||||
return () => h('output', drive.value.driveSpeed.toString());
|
||||
return () => h('output', drive.snapshot.value.driveSpeed.toString());
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -207,13 +207,14 @@ describe('useScrollDrive', () => {
|
||||
const documentAdd = vi.spyOn(document, 'addEventListener');
|
||||
const viewportAdd = vi.spyOn(visualViewport, 'addEventListener');
|
||||
|
||||
let binding!: ReturnType<typeof useScrollDrive>;
|
||||
const wrapper = mount(defineComponent({
|
||||
setup() {
|
||||
return { drive: useScrollDrive() };
|
||||
binding = useScrollDrive();
|
||||
return () => h('output', binding.snapshot.value.routeProgress.toString());
|
||||
},
|
||||
template: '<output>{{ drive.routeProgress }}</output>',
|
||||
}));
|
||||
const drive = () => wrapper.vm.drive;
|
||||
const drive = () => binding.snapshot.value;
|
||||
|
||||
expect(drive()).toMatchObject({
|
||||
scrollY: 250,
|
||||
@@ -306,4 +307,117 @@ describe('useScrollDrive', () => {
|
||||
document.dispatchEvent(new Event('visibilitychange'));
|
||||
expect(callbacks).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('keeps the shared frame bus alive only while a subscriber requests frames', async () => {
|
||||
const { useScrollDrive } = await import('@/composables/useScrollDrive');
|
||||
Object.defineProperty(document, 'visibilityState', {
|
||||
configurable: true,
|
||||
value: 'visible',
|
||||
});
|
||||
Object.defineProperties(document.documentElement, {
|
||||
scrollTop: { configurable: true, writable: true, value: 0 },
|
||||
scrollHeight: { configurable: true, value: 1000 },
|
||||
clientHeight: { configurable: true, value: 500 },
|
||||
});
|
||||
Object.defineProperty(window, 'visualViewport', {
|
||||
configurable: true,
|
||||
value: undefined,
|
||||
});
|
||||
Object.defineProperty(document, 'fonts', {
|
||||
configurable: true,
|
||||
value: { ready: Promise.resolve() },
|
||||
});
|
||||
|
||||
const callbacks = new Map<number, FrameRequestCallback>();
|
||||
let nextFrameId = 1;
|
||||
vi.stubGlobal('requestAnimationFrame', vi.fn((callback: FrameRequestCallback) => {
|
||||
const id = nextFrameId++;
|
||||
callbacks.set(id, callback);
|
||||
return id;
|
||||
}));
|
||||
vi.stubGlobal('cancelAnimationFrame', vi.fn((id: number) => callbacks.delete(id)));
|
||||
vi.spyOn(performance, 'now').mockReturnValue(100);
|
||||
|
||||
let binding!: ReturnType<typeof useScrollDrive>;
|
||||
const wrapper = mount(defineComponent({
|
||||
setup() {
|
||||
binding = useScrollDrive();
|
||||
return () => h('output', binding.snapshot.value.driveSpeed.toString());
|
||||
},
|
||||
}));
|
||||
let subscriberFrames = 0;
|
||||
const unsubscribe = binding.subscribeFrame((_now, frameSnapshot) => {
|
||||
expect(frameSnapshot).toEqual(binding.snapshot.value);
|
||||
subscriberFrames += 1;
|
||||
return subscriberFrames < 3;
|
||||
});
|
||||
|
||||
binding.requestFrame();
|
||||
for (const now of [116, 132, 148]) {
|
||||
expect(callbacks).toHaveLength(1);
|
||||
const requested = [...callbacks.entries()][0]!;
|
||||
callbacks.delete(requested[0]);
|
||||
requested[1](now);
|
||||
await nextTick();
|
||||
}
|
||||
|
||||
expect(subscriberFrames).toBe(3);
|
||||
expect(callbacks).toHaveLength(0);
|
||||
unsubscribe();
|
||||
binding.requestFrame();
|
||||
expect(callbacks).toHaveLength(1);
|
||||
wrapper.unmount();
|
||||
expect(callbacks).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('owns the only raw window scroll listener used by the complete journey binding', async () => {
|
||||
const { useJourneySections } = await import('@/composables/useJourneySections');
|
||||
Object.defineProperty(document, 'visibilityState', {
|
||||
configurable: true,
|
||||
value: 'visible',
|
||||
});
|
||||
Object.defineProperties(document.documentElement, {
|
||||
scrollTop: { configurable: true, writable: true, value: 0 },
|
||||
scrollHeight: { configurable: true, value: 2000 },
|
||||
clientHeight: { configurable: true, value: 800 },
|
||||
});
|
||||
Object.defineProperty(window, 'visualViewport', {
|
||||
configurable: true,
|
||||
value: undefined,
|
||||
});
|
||||
Object.defineProperty(document, 'fonts', {
|
||||
configurable: true,
|
||||
value: { ready: Promise.resolve() },
|
||||
});
|
||||
const callbacks = new Map<number, FrameRequestCallback>();
|
||||
let nextFrameId = 1;
|
||||
vi.stubGlobal('requestAnimationFrame', vi.fn((callback: FrameRequestCallback) => {
|
||||
const id = nextFrameId++;
|
||||
callbacks.set(id, callback);
|
||||
return id;
|
||||
}));
|
||||
vi.stubGlobal('cancelAnimationFrame', vi.fn((id: number) => callbacks.delete(id)));
|
||||
vi.spyOn(performance, 'now').mockReturnValue(0);
|
||||
const add = vi.spyOn(window, 'addEventListener');
|
||||
const ids = ['start', 'profile', 'contact'] as const;
|
||||
const navigation = shallowRef({
|
||||
epoch: 0,
|
||||
source: 'passive-scroll' as const,
|
||||
destinationId: null,
|
||||
});
|
||||
const motionAllowed = shallowRef(true);
|
||||
|
||||
const wrapper = mount(defineComponent({
|
||||
setup() {
|
||||
useJourneySections(ids, navigation, motionAllowed);
|
||||
return () => h('main', ids.map((id) => h('section', { id }, [
|
||||
h('h2', { id: `${id}-heading` }, id),
|
||||
])));
|
||||
},
|
||||
}), { attachTo: document.body });
|
||||
|
||||
expect(add.mock.calls.filter(([type]) => type === 'scroll')).toHaveLength(1);
|
||||
wrapper.unmount();
|
||||
expect(callbacks).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user