-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* FEAT/dispatcher added * FEAT/dispatcher tests added * FEAT/createApp fn added * FEAT/exports app from index.ts
- Loading branch information
1 parent
9511b68
commit 3371499
Showing
4 changed files
with
190 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { destroyDom } from "./destroy-dom"; | ||
import { Dispatcher } from "./dispatcher"; | ||
import { VNodes } from "./h"; | ||
import { mountDOM } from "./mount-dom"; | ||
|
||
type Props = { | ||
state: any; | ||
view: (state: any, emit: any) => VNodes; | ||
reducers: Record<string, Function>; | ||
}; | ||
|
||
export function createApp({ state, view, reducers }: Props) { | ||
let parentEl: HTMLElement | null = null; | ||
let vdom: VNodes | null = null; | ||
|
||
const dispatcher = new Dispatcher(); | ||
|
||
const emit = (eventName: string, payload: any) => { | ||
dispatcher.dispatch(eventName, payload); | ||
}; | ||
|
||
const renderApp = () => { | ||
if (vdom) { | ||
destroyDom(vdom); | ||
} | ||
|
||
vdom = view(state, emit); | ||
|
||
if (vdom && parentEl) { | ||
mountDOM(vdom, parentEl); | ||
} | ||
}; | ||
|
||
const subscriptions = [dispatcher.afterEveryCommand(renderApp)]; | ||
|
||
for (const actionName in reducers) { | ||
const reducer = reducers[actionName]; | ||
const subs = dispatcher.subscribe(actionName, (payload: any) => { | ||
state = reducer(state, payload); | ||
}); | ||
subscriptions.push(subs); | ||
} | ||
|
||
return { | ||
mount: (_parentEl: HTMLElement) => { | ||
parentEl = _parentEl; | ||
renderApp(); | ||
}, | ||
|
||
unmount: () => { | ||
if (vdom) { | ||
destroyDom(vdom); | ||
} | ||
|
||
vdom = null; | ||
|
||
for (const subscription of subscriptions) { | ||
subscription(); | ||
} | ||
}, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
export class Dispatcher { | ||
private subs = new Map<string, Function[]>(); | ||
private afterHandlers: Function[] = []; | ||
|
||
public subscribe = (commandName: string, handler: Function) => { | ||
if (!this.subs.has(commandName)) { | ||
this.subs.set(commandName, []); | ||
} | ||
|
||
const handlers = this.subs.get(commandName); | ||
|
||
if (!handlers) { | ||
throw new Error("handlers cannot be undefined"); | ||
} | ||
|
||
if (handlers.includes(handler)) { | ||
return () => {}; | ||
} | ||
|
||
handlers.push(handler); | ||
|
||
return () => { | ||
const idx = handlers.indexOf(handler); | ||
handlers.splice(idx, 1); | ||
}; | ||
}; | ||
|
||
public afterEveryCommand = (handler: Function) => { | ||
this.afterHandlers.push(handler); | ||
|
||
return () => { | ||
const idx = this.afterHandlers.indexOf(handler); | ||
this.afterHandlers.splice(idx, 1); | ||
}; | ||
}; | ||
|
||
public dispatch = (commandName: string, payload: any) => { | ||
const handlers = this.subs.get(commandName); | ||
|
||
if (handlers) { | ||
for (const handler of handlers) { | ||
handler(payload); | ||
} | ||
} else { | ||
console.warn(`No handlers for command: ${commandName}`); | ||
} | ||
|
||
for (const afterHandler of this.afterHandlers) { | ||
afterHandler(); | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export * from "./h"; | ||
export * from "./mount-dom"; | ||
export * from "./app"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { describe, expect, it, vi } from "vitest"; | ||
import { Dispatcher } from "../src/dispatcher"; | ||
|
||
const commandName = "test-event"; | ||
const payload = { test: "payload" }; | ||
|
||
describe("A command dispatcher", () => { | ||
it("can register and unregister handlers to specific commands", () => { | ||
const dispatcher = new Dispatcher(); | ||
const handler = vi.fn(); | ||
|
||
const unsubscribe = dispatcher.subscribe(commandName, handler); | ||
dispatcher.dispatch(commandName, payload); | ||
|
||
expect(handler).toHaveBeenCalledWith(payload); | ||
|
||
unsubscribe(); | ||
dispatcher.dispatch(commandName, payload); | ||
|
||
expect(handler).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it("can't register the same handler twice", () => { | ||
const dispatcher = new Dispatcher(); | ||
const handler = vi.fn(); | ||
|
||
const unsubscribe = dispatcher.subscribe(commandName, handler); | ||
dispatcher.subscribe(commandName, handler); | ||
dispatcher.subscribe(commandName, handler); | ||
|
||
dispatcher.dispatch(commandName, payload); | ||
|
||
expect(handler).toHaveBeenCalledTimes(1); | ||
|
||
unsubscribe(); | ||
dispatcher.dispatch(commandName, payload); | ||
|
||
expect(handler).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it("unsubscribe multiple handlers", () => { | ||
const dispatcher = new Dispatcher(); | ||
const handler1 = vi.fn(); | ||
const handler2 = vi.fn(); | ||
|
||
const unsubscribe1 = dispatcher.subscribe(commandName, handler1); | ||
const unsubscribe2 = dispatcher.subscribe(commandName, handler2); | ||
dispatcher.dispatch(commandName, payload); | ||
|
||
expect(handler1).toHaveBeenCalledWith(payload); | ||
expect(handler2).toHaveBeenCalledWith(payload); | ||
|
||
unsubscribe1(); | ||
unsubscribe2(); | ||
dispatcher.dispatch(commandName, payload); | ||
|
||
expect(handler1).toHaveBeenCalledTimes(1); | ||
expect(handler2).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it("can register and unregister handlers that run after each command", () => { | ||
const dispatcher = new Dispatcher(); | ||
const handler = vi.fn(); | ||
|
||
const unsubscribe = dispatcher.afterEveryCommand(handler); | ||
dispatcher.dispatch(commandName, payload); | ||
|
||
expect(handler).toHaveBeenCalled(); | ||
|
||
unsubscribe(); | ||
dispatcher.dispatch(commandName, payload); | ||
|
||
expect(handler).toHaveBeenCalledTimes(1); | ||
}); | ||
}); |