148 lines
5.7 KiB
TypeScript
148 lines
5.7 KiB
TypeScript
import { mount } from '@vue/test-utils';
|
|
import { describe, expect, it } from 'vitest';
|
|
import { defineComponent, h, nextTick, shallowRef } from 'vue';
|
|
import { useMaintenanceWindow } from '@/composables/useMaintenanceWindow';
|
|
import type { JourneySnapshot } from '@/journey/types';
|
|
import {
|
|
createMaintenanceDirection,
|
|
maintenanceClock,
|
|
maintenanceProgress,
|
|
maintenanceStep,
|
|
maintenanceSubline,
|
|
} from '@/journey/maintenance-progress';
|
|
import type { SectionId } from '@/content/types';
|
|
|
|
const at = (
|
|
activeSectionId: SectionId,
|
|
activeHeadingOffsetVh: number,
|
|
nextHeadingOffsetVh: number | null,
|
|
) => ({
|
|
activeSectionId,
|
|
sectionActivation: { layoutRevision: 1, activeHeadingOffsetVh, nextHeadingOffsetVh },
|
|
});
|
|
|
|
const live = () => '14:07';
|
|
|
|
describe('maintenance progress', () => {
|
|
it('is 0 at the Infrastructure heading and 1 at the next heading', () => {
|
|
expect(maintenanceProgress(at('infrastructure', 0, 2.5))).toBe(0);
|
|
expect(maintenanceProgress(at('infrastructure', -1.25, 1.25))).toBeCloseTo(0.5);
|
|
expect(maintenanceProgress(at('infrastructure', -2.5, 0))).toBe(1);
|
|
});
|
|
|
|
it('clamps to the window and is null outside the chapter or without geometry', () => {
|
|
expect(maintenanceProgress(at('infrastructure', 0.2, 2.7))).toBe(0);
|
|
expect(maintenanceProgress(at('infrastructure', -3, -0.5))).toBe(1);
|
|
expect(maintenanceProgress(at('experience', -1, 1))).toBeNull();
|
|
expect(maintenanceProgress(at('selected-builds', -1, 1))).toBeNull();
|
|
expect(maintenanceProgress(at('infrastructure', 0, null))).toBeNull();
|
|
expect(maintenanceProgress(at('infrastructure', 1, 1))).toBeNull();
|
|
expect(maintenanceProgress(at('infrastructure', Number.NaN, 1))).toBeNull();
|
|
});
|
|
|
|
it('maps progress onto open, backup, transfer, verify and done', () => {
|
|
expect(maintenanceStep(0)).toBe('open');
|
|
expect(maintenanceStep(0.099)).toBe('open');
|
|
expect(maintenanceStep(0.1)).toBe('backup');
|
|
expect(maintenanceStep(0.349)).toBe('backup');
|
|
expect(maintenanceStep(0.35)).toBe('transfer');
|
|
expect(maintenanceStep(0.649)).toBe('transfer');
|
|
expect(maintenanceStep(0.65)).toBe('verify');
|
|
expect(maintenanceStep(0.899)).toBe('verify');
|
|
expect(maintenanceStep(0.9)).toBe('done');
|
|
expect(maintenanceStep(1)).toBe('done');
|
|
});
|
|
|
|
it('holds the clock at 03:12 while the window opens, cuts over at 03:51, then goes live', () => {
|
|
expect(maintenanceClock(0, live)).toBe('03:12');
|
|
expect(maintenanceClock(0.1, live)).toBe('03:12');
|
|
expect(maintenanceClock(0.5, live)).toBe('03:32');
|
|
expect(maintenanceClock(0.9, live)).toBe('03:51');
|
|
expect(maintenanceClock(0.999, live)).toBe('03:51');
|
|
expect(maintenanceClock(1, live)).toBe('14:07');
|
|
let previous = -1;
|
|
for (let p = 0; p < 1; p += 0.01) {
|
|
const minutes = Number(maintenanceClock(p, live).slice(3));
|
|
expect(minutes).toBeGreaterThanOrEqual(previous);
|
|
previous = minutes;
|
|
}
|
|
});
|
|
|
|
it('writes the sub-line from the step, and a rollback while reversing', () => {
|
|
expect(maintenanceSubline(0.02, false, live)).toBe('MAINTENANCE WINDOW · 03:12');
|
|
expect(maintenanceSubline(0.92, false, live)).toBe('CUTOVER OK · 03:51');
|
|
expect(maintenanceSubline(0.5, true, live)).toMatch(/^ROLLBACK CHECKPOINT · 03:\d\d$/u);
|
|
expect(maintenanceSubline(0.97, false, live)).toBe('CUTOVER OK · 03:51');
|
|
});
|
|
|
|
it('latches a rollback while progress falls and clears it when it rises', () => {
|
|
const track = createMaintenanceDirection();
|
|
expect(track(0.2)).toBe(false);
|
|
expect(track(0.3)).toBe(false);
|
|
expect(track(0.25)).toBe(true);
|
|
expect(track(0.25)).toBe(true);
|
|
expect(track(0.2505)).toBe(true);
|
|
expect(track(0.3)).toBe(false);
|
|
expect(track(0.2)).toBe(true);
|
|
expect(track(null)).toBe(false);
|
|
expect(track(0.9)).toBe(false);
|
|
expect(track(0)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('useMaintenanceWindow', () => {
|
|
const snapshot = (activeSectionId: SectionId, a: number, n: number | null): JourneySnapshot => ({
|
|
scrollY: 0,
|
|
routeProgress: 0.4,
|
|
direction: 0,
|
|
acceleration: 0,
|
|
driveSpeed: 0,
|
|
activeSectionId,
|
|
chapter: 'infrastructure',
|
|
sign: null,
|
|
arrived: false,
|
|
sectionActivation: { layoutRevision: 1, activeHeadingOffsetVh: a, nextHeadingOffsetVh: n },
|
|
});
|
|
|
|
it('steps html[data-maintenance-step] and the sub-line from scroll, then cleans up', async () => {
|
|
const journey = shallowRef(snapshot('experience', -1, 1));
|
|
let subline = shallowRef('');
|
|
const wrapper = mount(defineComponent({
|
|
setup() {
|
|
subline = useMaintenanceWindow(journey) as typeof subline;
|
|
return () => h('span', subline.value);
|
|
},
|
|
}));
|
|
const root = document.documentElement;
|
|
await nextTick();
|
|
expect(root.dataset.maintenanceStep).toBeUndefined();
|
|
expect(subline.value).toBe('');
|
|
|
|
journey.value = snapshot('infrastructure', 0, 2);
|
|
await nextTick();
|
|
expect(root.dataset.maintenanceStep).toBe('open');
|
|
expect(subline.value).toBe('MAINTENANCE WINDOW \u00b7 03:12');
|
|
|
|
journey.value = snapshot('infrastructure', -1.84, 0.16);
|
|
await nextTick();
|
|
expect(root.dataset.maintenanceStep).toBe('done');
|
|
expect(subline.value).toBe('CUTOVER OK \u00b7 03:51');
|
|
|
|
journey.value = snapshot('infrastructure', -1, 1);
|
|
await nextTick();
|
|
expect(root.dataset.maintenanceStep).toBe('transfer');
|
|
expect(subline.value).toMatch(/^ROLLBACK CHECKPOINT/u);
|
|
|
|
journey.value = snapshot('selected-builds', 0, 2);
|
|
await nextTick();
|
|
expect(root.dataset.maintenanceStep).toBeUndefined();
|
|
expect(subline.value).toBe('');
|
|
|
|
journey.value = snapshot('infrastructure', -0.4, 1.6);
|
|
await nextTick();
|
|
expect(root.dataset.maintenanceStep).toBe('backup');
|
|
wrapper.unmount();
|
|
expect(root.dataset.maintenanceStep).toBeUndefined();
|
|
});
|
|
});
|