Skip to content

Commit

Permalink
fix: remove xstate
Browse files Browse the repository at this point in the history
  • Loading branch information
malcolm-kee committed Nov 27, 2022
1 parent a61563c commit 7907c8e
Show file tree
Hide file tree
Showing 9 changed files with 717 additions and 597 deletions.
8 changes: 4 additions & 4 deletions client-src/index.js
Expand Up @@ -142,7 +142,7 @@ const onSocketMessage = {

// Fixes #1042. overlay doesn't clear if errors are fixed but warnings remain.
if (options.overlay) {
overlay.send("DISMISS");
overlay.send({ type: "DISMISS" });
}

sendMessage("Invalid");
Expand Down Expand Up @@ -199,7 +199,7 @@ const onSocketMessage = {
log.info("Nothing changed.");

if (options.overlay) {
overlay.send("DISMISS");
overlay.send({ type: "DISMISS" });
}

sendMessage("StillOk");
Expand All @@ -208,7 +208,7 @@ const onSocketMessage = {
sendMessage("Ok");

if (options.overlay) {
overlay.send("DISMISS");
overlay.send({ type: "DISMISS" });
}

reloadApp(options, status);
Expand Down Expand Up @@ -317,7 +317,7 @@ const onSocketMessage = {
log.info("Disconnected!");

if (options.overlay) {
overlay.send("DISMISS");
overlay.send({ type: "DISMISS" });
}

sendMessage("Close");
Expand Down
8 changes: 2 additions & 6 deletions client-src/overlay.js
Expand Up @@ -3,7 +3,6 @@

import ansiHTML from "ansi-html-community";
import { encode } from "html-entities";
import { interpret } from "@xstate/fsm";
import {
containerStyle,
dismissButtonStyle,
Expand Down Expand Up @@ -143,7 +142,7 @@ const createOverlay = (options) => {
closeButtonElement.ariaLabel = "Dismiss";
closeButtonElement.addEventListener("click", () => {
// eslint-disable-next-line no-use-before-define
overlayService.send("DISMISS");
overlayService.send({ type: "DISMISS" });
});

contentElement.appendChild(headerElement);
Expand Down Expand Up @@ -254,16 +253,13 @@ const createOverlay = (options) => {
}, trustedTypesPolicyName);
}

const overlayMachine = createOverlayMachine({
const overlayService = createOverlayMachine({
showOverlay: ({ level = "error", messages }) =>
show(level, messages, options.trustedTypesPolicyName),
hideOverlay: hide,
});

const overlayService = interpret(overlayMachine).start();

listenToRuntimeError((err) => {
console.log(err);
overlayService.send({
type: "RUNTIME_ERROR",
messages: [err.message],
Expand Down
57 changes: 57 additions & 0 deletions client-src/overlay/fsm.js
@@ -0,0 +1,57 @@
/**
* @typedef {Object} StateDefinitions
* @property {{[event: string]: { target: string; actions?: Array<string> }}} [on]
*/

/**
* @typedef {Object} Options
* @property {{[state: string]: StateDefinitions}} states
* @property {object} context;
* @property {string} initial
*/

/**
* @typedef {Object} Implementation
* @property {{[actionName: string]: (ctx: object, event: any) => object}} actions
*/

/**
* A simplified `createMachine` from `@xstate/fsm` with the following differences:
*
* - the returned machine is technically a "service". No `interpret(machine).start()` is needed.
* - the state definition only support `on` and target must be declared with { target: 'nextState', actions: [] } explicitly.
* - event passed to `send` must be an object with `type` property.
* - actions implementation will be [assign action](https://xstate.js.org/docs/guides/context.html#assign-action) if you return any value.
* Do not return anything if you just want to invoke side effect.
*
* The goal of this custom function is to avoid installing the entire `'xstate/fsm'` package, while enabling modeling using
* state machine. You can copy the first parameter into the editor at https://stately.ai/viz to visualize the state machine.
*
* @param {Options} options
* @param {Implementation} implementation
*/
function createMachine({ states, context, initial }, { actions }) {
let currentState = initial;
let currentContext = context;

return {
send: (event) => {
const transitionConfig = states[currentState].on?.[event.type];

if (transitionConfig) {
currentState = transitionConfig.target;
transitionConfig.actions?.forEach((actName) => {
const nextContextValue = actions[actName]?.(currentContext, event);
if (nextContextValue) {
currentContext = {
...currentContext,
...nextContextValue,
};
}
});
}
},
};
}

export default createMachine;
44 changes: 27 additions & 17 deletions client-src/overlay/state-machine.js
@@ -1,4 +1,4 @@
import { createMachine, assign } from "@xstate/fsm";
import createMachine from "./fsm.js";

/**
* @typedef {Object} ShowOverlayData
Expand All @@ -19,15 +19,13 @@ const createOverlayMachine = (options) => {
const { hideOverlay, showOverlay } = options;
const overlayMachine = createMachine(
{
id: "overlay",
initial: "hidden",
context: {
level: "error",
messages: [],
},
states: {
hidden: {
entry: "hideOverlay",
on: {
BUILD_ERROR: {
target: "displayBuildError",
Expand All @@ -41,7 +39,10 @@ const createOverlayMachine = (options) => {
},
displayBuildError: {
on: {
DISMISS: { target: "hidden", actions: "dismissMessages" },
DISMISS: {
target: "hidden",
actions: ["dismissMessages", "hideOverlay"],
},
BUILD_ERROR: {
target: "displayBuildError",
actions: ["appendMessages", "showOverlay"],
Expand All @@ -50,7 +51,10 @@ const createOverlayMachine = (options) => {
},
displayRuntimeError: {
on: {
DISMISS: { target: "hidden", actions: "dismissMessages" },
DISMISS: {
target: "hidden",
actions: ["dismissMessages", "hideOverlay"],
},
RUNTIME_ERROR: {
target: "displayRuntimeError",
actions: ["appendMessages", "showOverlay"],
Expand All @@ -65,18 +69,24 @@ const createOverlayMachine = (options) => {
},
{
actions: {
dismissMessages: assign({
messages: [],
level: "error",
}),
appendMessages: assign({
messages: (context, event) => context.messages.concat(event.messages),
level: (context, event) => event.level || context.level,
}),
setMessages: assign({
messages: (_, event) => event.messages,
level: (context, event) => event.level || context.level,
}),
dismissMessages: () => {
return {
messages: [],
level: "error",
};
},
appendMessages: (context, event) => {
return {
messages: context.messages.concat(event.messages),
level: event.level || context.level,
};
},
setMessages: (context, event) => {
return {
messages: event.messages,
level: event.level || context.level,
};
},
hideOverlay,
showOverlay,
},
Expand Down
11 changes: 0 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Expand Up @@ -51,7 +51,6 @@
"@types/serve-static": "^1.13.10",
"@types/sockjs": "^0.3.33",
"@types/ws": "^8.5.1",
"@xstate/fsm": "^2.0.0",
"ansi-html-community": "^0.0.8",
"bonjour-service": "^1.0.11",
"chokidar": "^3.5.3",
Expand Down
63 changes: 47 additions & 16 deletions test/client/index.test.js
Expand Up @@ -35,15 +35,21 @@ describe("index", () => {
jest.setMock("../../client-src/socket.js", jest.fn());
socket = require("../../client-src/socket");

const send = jest.fn();

// overlay
jest.setMock("../../client-src/overlay.js", {
hide: jest.fn(),
show: jest.fn(),
createOverlay: () => {
return {
send,
};
},
formatProblem: (item) => {
return { header: "HEADER warning", body: `BODY: ${item}` };
},
});
overlay = require("../../client-src/overlay");
const { createOverlay } = require("../../client-src/overlay");
overlay = createOverlay();

// reloadApp
jest.setMock("../../client-src/utils/reloadApp.js", jest.fn());
Expand Down Expand Up @@ -89,13 +95,13 @@ describe("index", () => {

expect(log.log.info.mock.calls[0][0]).toMatchSnapshot();
expect(sendMessage.mock.calls[0][0]).toMatchSnapshot();
expect(overlay.hide).not.toBeCalled();
expect(overlay.send).not.toBeCalledWith({ type: "DISMISS" });

// change flags
onSocketMessage.overlay(true);
onSocketMessage["still-ok"]();

expect(overlay.hide).toBeCalled();
expect(overlay.send).toHaveBeenCalledWith({ type: "DISMISS" });
});

test("should run onSocketMessage.progress and onSocketMessage['progress-update']", () => {
Expand Down Expand Up @@ -191,9 +197,14 @@ describe("index", () => {

// change flags
onSocketMessage.overlay({ warnings: true });
onSocketMessage.warnings([]);
onSocketMessage.warnings(["warning message"]);

expect(overlay.show).toBeCalled();
expect(overlay.send).toHaveBeenCalledTimes(1);
expect(overlay.send).toHaveBeenCalledWith({
type: "BUILD_ERROR",
level: "warning",
messages: ["warning message"],
});
});

test("should parse overlay options from resource query", () => {
Expand All @@ -202,51 +213,71 @@ describe("index", () => {
global.__resourceQuery = `?overlay=${encodeURIComponent(
`{"warnings": false}`
)}`;
overlay.show.mockReset();
overlay.send.mockReset();
socket.mockReset();
jest.unmock("../../client-src/utils/parseURL.js");
require("../../client-src");
onSocketMessage = socket.mock.calls[0][1];

onSocketMessage.warnings(["warn1"]);
expect(overlay.show).not.toBeCalled();
expect(overlay.send).not.toBeCalled();

onSocketMessage.errors(["error1"]);
expect(overlay.show).toBeCalledTimes(1);
expect(overlay.send).toBeCalledTimes(1);
expect(overlay.send).toHaveBeenCalledWith({
type: "BUILD_ERROR",
level: "error",
messages: ["error1"],
});
});

jest.isolateModules(() => {
// Pass JSON config with errors disabled
global.__resourceQuery = `?overlay=${encodeURIComponent(
`{"errors": false}`
)}`;
overlay.show.mockReset();
overlay.send.mockReset();
socket.mockReset();
jest.unmock("../../client-src/utils/parseURL.js");
require("../../client-src");
onSocketMessage = socket.mock.calls[0][1];

onSocketMessage.errors(["error1"]);
expect(overlay.show).not.toBeCalled();
expect(overlay.send).not.toBeCalled();

onSocketMessage.warnings(["warn1"]);
expect(overlay.show).toBeCalledTimes(1);
expect(overlay.send).toBeCalledTimes(1);
expect(overlay.send).toHaveBeenCalledWith({
type: "BUILD_ERROR",
level: "warning",
messages: ["warn1"],
});
});

jest.isolateModules(() => {
// Use simple boolean
global.__resourceQuery = "?overlay=true";
jest.unmock("../../client-src/utils/parseURL.js");
socket.mockReset();
overlay.show.mockReset();
overlay.send.mockReset();
require("../../client-src");
onSocketMessage = socket.mock.calls[0][1];

onSocketMessage.warnings(["warn2"]);
expect(overlay.show).toBeCalledTimes(1);
expect(overlay.send).toBeCalledTimes(1);
expect(overlay.send).toHaveBeenLastCalledWith({
type: "BUILD_ERROR",
level: "warning",
messages: ["warn2"],
});

onSocketMessage.errors(["error2"]);
expect(overlay.show).toBeCalledTimes(2);
expect(overlay.send).toBeCalledTimes(2);
expect(overlay.send).toHaveBeenLastCalledWith({
type: "BUILD_ERROR",
level: "error",
messages: ["error2"],
});
});
});

Expand Down

0 comments on commit 7907c8e

Please sign in to comment.