Skip to content

Commit 589b6cd

Browse files
committed
Custom event with value, add test for online/offline
1 parent a7e63a5 commit 589b6cd

File tree

2 files changed

+120
-3
lines changed

2 files changed

+120
-3
lines changed

src/index.test.ts

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,21 @@ import {
1313
send,
1414
start,
1515
accumulate,
16+
onceStateChangesTo,
1617
} from "./index";
1718

1819
test("node version " + process.version, () => {});
1920

21+
function useEach(work: () => () => void) {
22+
let cleanup: null | (() => void) = null;
23+
beforeEach(() => {
24+
cleanup = work();
25+
})
26+
afterEach(() => {
27+
cleanup?.call(null);
28+
});
29+
}
30+
2031
const fetch = jest.fn();
2132
beforeEach(fetch.mockClear);
2233

@@ -449,18 +460,35 @@ describe("Switch", () => {
449460
machine.next("FLICK");
450461
expect(machine.current).toEqual("ON");
451462
expect(eventListener).toHaveBeenCalledTimes(1);
452-
expect(eventListener).toHaveBeenLastCalledWith(expect.objectContaining({ type: "StateChanged" }));
463+
expect(eventListener).toHaveBeenLastCalledWith(expect.objectContaining({ type: "StateChanged", value: "ON" }));
453464

454465
machine.next("FLICK");
455466
expect(machine.current).toEqual("OFF");
456467
expect(eventListener).toHaveBeenCalledTimes(2);
468+
expect(eventListener).toHaveBeenLastCalledWith(expect.objectContaining({ type: "StateChanged", value: "OFF" }));
457469

458470
machine.signal.removeEventListener("StateChanged", eventListener);
459471

460472
machine.next("FLICK");
461473
expect(machine.current).toEqual("ON");
462474
expect(eventListener).toHaveBeenCalledTimes(2);
463475
});
476+
477+
it("can produce a promise that resolves when state changes to ON", async () => {
478+
const machine = start(Switch);
479+
480+
const whenPromiseResolves = jest.fn();
481+
const aborter = new AbortController();
482+
const onPromise = onceStateChangesTo(machine, "ON", aborter.signal)
483+
onPromise.then(whenPromiseResolves)
484+
485+
await null;
486+
expect(whenPromiseResolves).toHaveBeenCalledTimes(0);
487+
488+
machine.next("FLICK");
489+
await null;
490+
expect(whenPromiseResolves).toHaveBeenCalledTimes(1);
491+
})
464492
});
465493

466494
describe("Switch with symbol messages", () => {
@@ -496,6 +524,71 @@ describe("Switch with symbol messages", () => {
496524
});
497525
});
498526

527+
describe("Wrapping navigator online as a state machine", () => {
528+
function* OfflineStatus() {
529+
yield listenTo(window, "online");
530+
yield listenTo(window, "offline");
531+
yield on("online", compound(Online));
532+
yield on("offline", compound(Offline));
533+
534+
function* Online() {}
535+
function* Offline() {}
536+
537+
return function* Pending() {
538+
yield cond(navigator.onLine, Online);
539+
yield always(Offline);
540+
}
541+
}
542+
543+
describe("when online", () => {
544+
useEach(() => {
545+
const spy = jest.spyOn(navigator, 'onLine', 'get').mockReturnValue(true);
546+
return () => spy.mockRestore();
547+
});
548+
549+
it("is immediately in Online state", () => {
550+
const machine = start(OfflineStatus);
551+
expect(machine.current).toEqual("Online");
552+
expect(machine.changeCount).toEqual(0);
553+
});
554+
555+
it("reacts to offline & online events", () => {
556+
const machine = start(OfflineStatus);
557+
window.dispatchEvent(new Event('offline'))
558+
expect(machine.current).toEqual("Offline");
559+
expect(machine.changeCount).toEqual(1);
560+
561+
window.dispatchEvent(new Event('online'))
562+
expect(machine.current).toEqual("Online");
563+
expect(machine.changeCount).toEqual(2);
564+
});
565+
});
566+
567+
describe("when offline", () => {
568+
useEach(() => {
569+
const spy = jest.spyOn(navigator, 'onLine', 'get').mockReturnValue(false);
570+
return () => spy.mockRestore();
571+
});
572+
573+
it("is immediately in Offline state", () => {
574+
const machine = start(OfflineStatus);
575+
expect(machine.current).toEqual("Offline");
576+
expect(machine.changeCount).toEqual(0);
577+
});
578+
579+
it("reacts to online & offline events", () => {
580+
const machine = start(OfflineStatus);
581+
window.dispatchEvent(new Event('online'))
582+
expect(machine.current).toEqual("Online");
583+
expect(machine.changeCount).toEqual(1);
584+
585+
window.dispatchEvent(new Event('offline'))
586+
expect(machine.current).toEqual("Offline");
587+
expect(machine.changeCount).toEqual(2);
588+
});
589+
});
590+
});
591+
499592
describe("Wrapping AbortController as a state machine", () => {
500593
function AbortSender(controller: AbortController) {
501594
function* initial() {

src/index.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,15 @@ class InternalInstance {
444444
}
445445
}
446446

447+
class MachineStateChangedEvent extends Event {
448+
readonly value: string;
449+
450+
constructor(type: string, value: string) {
451+
super(type)
452+
this.value = value;
453+
}
454+
}
455+
447456
export function start(
448457
machine: (() => StateDefinition) | (() => Generator<Yielded, StateDefinition, never>)
449458
): MachineInstance {
@@ -474,7 +483,7 @@ export function start(
474483
_changeCount += 1;
475484
},
476485
didChangeState() {
477-
_aborter?.signal.dispatchEvent(new Event('StateChanged'));
486+
_aborter?.signal.dispatchEvent(new MachineStateChangedEvent('StateChanged', getCurrent() as string));
478487
},
479488
didChangeAccumulations() {
480489
_aborter?.signal.dispatchEvent(new Event('AccumulationsChanged'));
@@ -487,6 +496,11 @@ export function start(
487496
}
488497
}
489498
);
499+
500+
function getCurrent() {
501+
const current = instance.current;
502+
return current !== null ? current[rootName] : null;
503+
}
490504

491505
_changeCount = 0;
492506

@@ -495,7 +509,7 @@ export function start(
495509
return _changeCount;
496510
},
497511
get current() {
498-
return instance.current !== null ? instance.current[rootName] : null;
512+
return getCurrent();
499513
},
500514
get signal() {
501515
return ensureAborter().signal;
@@ -524,3 +538,13 @@ export function start(
524538
},
525539
};
526540
}
541+
542+
export function onceStateChangesTo(machineInstance: MachineInstance, state: string, signal: AbortSignal): Promise<void> {
543+
return new Promise((resolve) => {
544+
machineInstance.signal.addEventListener("StateChanged", (event: MachineStateChangedEvent) => {
545+
if (event.value === state) {
546+
resolve();
547+
}
548+
}, { signal });
549+
})
550+
}

0 commit comments

Comments
 (0)