120 lines
4.7 KiB
TypeScript
120 lines
4.7 KiB
TypeScript
import { readFileSync } from 'node:fs';
|
|
import { resolve } from 'node:path';
|
|
import { mount } from '@vue/test-utils';
|
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
import { defineComponent, h, nextTick, shallowRef } from 'vue';
|
|
import { useRelay } from '@/composables/useRelay';
|
|
import { BEACON_HZ, publishRelay, readRelay, RELAY_STOPS, relayOnline, rungLit } from '@/journey/relay-progress';
|
|
import type { JourneySnapshot } from '@/journey/types';
|
|
import type { PortfolioContent, SectionId } from '@/content/types';
|
|
|
|
const content = JSON.parse(
|
|
readFileSync(resolve(process.cwd(), 'content/public/portfolio-content.json'), 'utf8'),
|
|
) as PortfolioContent;
|
|
const counts = content.toolkit.map((group) => group.technologies.length);
|
|
|
|
const sweep = (from: number, to: number, steps = 400): number[] =>
|
|
Array.from({ length: steps + 1 }, (_, i) => from + ((to - from) * i) / steps);
|
|
|
|
afterEach(() => publishRelay([]));
|
|
|
|
describe('relay progress', () => {
|
|
it('has one stop per group and blinks well under 3 Hz', () => {
|
|
expect(RELAY_STOPS).toHaveLength(4);
|
|
expect(counts).toHaveLength(4);
|
|
expect(BEACON_HZ).toBeLessThanOrEqual(3);
|
|
});
|
|
|
|
it('brings masts online monotonically and takes them offline in reverse', () => {
|
|
const forward = sweep(0, 1).map(relayOnline);
|
|
forward.forEach((n, i) => { if (i > 0) expect(n).toBeGreaterThanOrEqual(forward[i - 1]!); });
|
|
const backward = [...sweep(0, 1)].reverse().map(relayOnline);
|
|
expect(backward).toEqual([...forward].reverse());
|
|
expect(relayOnline(null)).toBe(0);
|
|
expect(relayOnline(0)).toBe(0);
|
|
expect(relayOnline(0.6)).toBe(4);
|
|
});
|
|
|
|
it('lights every rung monotonically and reversibly, all lit by the end of the ramp', () => {
|
|
counts.forEach((count, mast) => {
|
|
for (let rung = 0; rung < count; rung += 1) {
|
|
const forward = sweep(0, 1).map((p) => rungLit(p, mast, rung, count));
|
|
forward.forEach((v, i) => { if (i > 0) expect(v).toBeGreaterThanOrEqual(forward[i - 1]!); });
|
|
expect([...sweep(0, 1)].reverse().map((p) => rungLit(p, mast, rung, count))).toEqual([...forward].reverse());
|
|
expect(rungLit(RELAY_STOPS[mast]! - 0.001, mast, rung, count)).toBe(0);
|
|
expect(rungLit(0.71, mast, rung, count)).toBe(1);
|
|
}
|
|
});
|
|
expect(rungLit(null, 0, 0, 3)).toBe(0);
|
|
});
|
|
|
|
it('lights rungs bottom-up in index order', () => {
|
|
for (const p of sweep(0, 1)) {
|
|
counts.forEach((count, mast) => {
|
|
for (let rung = 1; rung < count; rung += 1) {
|
|
expect(rungLit(p, mast, rung, count)).toBeLessThanOrEqual(rungLit(p, mast, rung - 1, count));
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
it('carries the rung counts from the DOM to the scene', () => {
|
|
expect(readRelay().counts).toEqual([]);
|
|
publishRelay(counts);
|
|
expect(readRelay().counts).toEqual([11, 8, 4, 3]);
|
|
publishRelay([1, 2, 3, 4, 5]);
|
|
expect(readRelay().counts).toHaveLength(4);
|
|
});
|
|
});
|
|
|
|
describe('useRelay', () => {
|
|
const snapshot = (activeSectionId: SectionId, a: number, n: number | null): JourneySnapshot => ({
|
|
scrollY: 0,
|
|
routeProgress: 0.8,
|
|
direction: 0,
|
|
acceleration: 0,
|
|
driveSpeed: 0,
|
|
activeSectionId,
|
|
chapter: 'systems',
|
|
sign: null,
|
|
arrived: false,
|
|
sectionActivation: { layoutRevision: 1, activeHeadingOffsetVh: a, nextHeadingOffsetVh: n },
|
|
} as unknown as JourneySnapshot);
|
|
|
|
it('sets html[data-relay-online] through Toolkit only, publishes counts, and cleans up', async () => {
|
|
const journey = shallowRef(snapshot('additional-work', -0.5, 1.5));
|
|
const wrapper = mount(defineComponent({
|
|
setup() {
|
|
useRelay(journey, () => content.toolkit);
|
|
return () => h('span');
|
|
},
|
|
}));
|
|
const root = document.documentElement;
|
|
await nextTick();
|
|
expect(readRelay().counts).toEqual(counts);
|
|
expect(root.dataset.relayOnline).toBeUndefined();
|
|
|
|
journey.value = snapshot('toolkit', -0.1, 1.9); // p 0.05
|
|
await nextTick();
|
|
expect(root.dataset.relayOnline).toBe('1');
|
|
journey.value = snapshot('toolkit', -1, 1); // p 0.5
|
|
await nextTick();
|
|
expect(root.dataset.relayOnline).toBe('3');
|
|
journey.value = snapshot('toolkit', -1.3, 0.7); // p 0.65
|
|
await nextTick();
|
|
expect(root.dataset.relayOnline).toBe('4');
|
|
journey.value = snapshot('toolkit', -0.5, 1.5); // p 0.25, scrolling back
|
|
await nextTick();
|
|
expect(root.dataset.relayOnline).toBe('2');
|
|
journey.value = snapshot('contact', -0.2, null);
|
|
await nextTick();
|
|
expect(root.dataset.relayOnline).toBeUndefined();
|
|
journey.value = snapshot('toolkit', -1, 1);
|
|
await nextTick();
|
|
expect(root.dataset.relayOnline).toBe('3');
|
|
wrapper.unmount();
|
|
expect(root.dataset.relayOnline).toBeUndefined();
|
|
expect(root.dataset.relayFocus).toBeUndefined();
|
|
});
|
|
});
|