Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AsyncNodePlugin w/ bazel 6 #269

Merged
merged 12 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .bazelignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ react/player/node_modules
react/subscribe/node_modules
plugins/asset-provider/react/node_modules
plugins/asset-transform/core/node_modules
plugins/async-node/core/node_modules
plugins/common-types/core/node_modules
plugins/types-provider/core/node_modules
plugins/auto-scroll/react/node_modules
Expand Down
31 changes: 31 additions & 0 deletions core/player/src/controllers/flow/__tests__/flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,37 @@ test("Fails to transition when not started", () => {
expect(() => flow.transition("foo")).toThrowError();
});

test("Fails to transition during another transition", () => {
const flow = new FlowInstance("flow", {
startState: "View1",
View1: {
state_type: "VIEW",
onStart: "foo bar",
ref: "foo",
transitions: {
Next: "View2",
},
},
View2: {
state_type: "VIEW",
ref: "bar",
transitions: {
Next: "View3",
},
},
});

flow.hooks.resolveTransitionNode.intercept({
call: (nextState) => {
if (nextState?.onStart) {
expect(() => flow.transition("Next")).toThrowError();
}
},
});

flow.start();
});

describe("promise api", () => {
it("resolves when were done", async () => {
const flow = new FlowInstance("flow", {
Expand Down
10 changes: 10 additions & 0 deletions core/player/src/controllers/flow/flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export class FlowInstance {
private flow: NavigationFlow;
private log?: Logger;
private history: string[];
private isTransitioning = false;
private flowPromise?: DeferredPromise<NavigationFlowEndState>;
public readonly id: string;
public currentState?: NamedState;
Expand Down Expand Up @@ -115,6 +116,12 @@ export class FlowInstance {
}

public transition(transitionValue: string, options?: TransitionOptions) {
if (this.isTransitioning) {
throw new Error(
`Transitioning while ongoing transition from ${this.currentState?.name} is in progress is not supported`,
);
}

if (this.currentState?.value.state_type === "END") {
this.log?.warn(
`Skipping transition using ${transitionValue}. Already at and END state`,
Expand Down Expand Up @@ -186,6 +193,7 @@ export class FlowInstance {

const prevState = this.currentState;

this.isTransitioning = true;
nextState = this.hooks.resolveTransitionNode.call(nextState);

const newCurrentState = {
Expand All @@ -205,6 +213,8 @@ export class FlowInstance {
...newCurrentState,
});

this.isTransitioning = false;

this.hooks.afterTransition.call(this);
}
}
2 changes: 2 additions & 0 deletions core/player/src/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type {
ErrorState,
} from "./types";
import { NOT_STARTED_STATE } from "./types";
import { DefaultViewPlugin } from "./plugins/default-view-plugin";

// Variables injected at build time
const PLAYER_VERSION = "__VERSION__";
Expand Down Expand Up @@ -136,6 +137,7 @@ export class Player {
this.config = config || {};
this.config.plugins = [
new DefaultExpPlugin(),
new DefaultViewPlugin(),
...(this.config.plugins || []),
new FlowExpPlugin(),
];
Expand Down
29 changes: 29 additions & 0 deletions core/player/src/plugins/default-view-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { Player, PlayerPlugin } from "../player";
import {
ApplicabilityPlugin,
StringResolverPlugin,
SwitchPlugin,
TemplatePlugin,
toNodeResolveOptions,
} from "../view";

/**
* A plugin that provides the out-of-the-box expressions for player
*/
export class DefaultViewPlugin implements PlayerPlugin {
name = "default-view-plugin";

apply(player: Player) {
player.hooks.viewController.tap(this.name, (viewController) => {
viewController.hooks.view.tap(this.name, (view) => {
const pluginOptions = toNodeResolveOptions(view.resolverOptions);
new SwitchPlugin(pluginOptions).apply(view);
new ApplicabilityPlugin().apply(view);
new StringResolverPlugin().apply(view);
const templatePlugin = new TemplatePlugin(pluginOptions);
templatePlugin.apply(view);
view.hooks.onTemplatePluginCreated.call(templatePlugin);
});
});
}
}
13 changes: 12 additions & 1 deletion core/player/src/view/__tests__/view.immutable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { LocalModel, withParser, PipelinedDataModel } from "../../data";
import { ExpressionEvaluator } from "../../expressions";
import { BindingParser } from "../../binding";
import { SchemaController } from "../../schema";
import { ViewInstance } from "..";
import { ApplicabilityPlugin, StringResolverPlugin, ViewInstance } from "..";
import { NodeType } from "../parser";

const parseBinding = new BindingParser().parse;
Expand Down Expand Up @@ -33,6 +33,8 @@ test("uses the exact same object if nothing changes", () => {
},
);

new StringResolverPlugin().apply(view);

view.hooks.resolver.tap("input", (resolver) => {
resolver.hooks.resolve.tap("input", (value, astNode, options) => {
if (astNode.type === NodeType.Asset && astNode.value.type === "input") {
Expand Down Expand Up @@ -90,6 +92,9 @@ test("applicability is immutable", () => {
},
);

new StringResolverPlugin().apply(view);
new ApplicabilityPlugin().apply(view);

const resolved = view.update();
const batUpdate = view.update(new Set([parseBinding("foo.bat")]));

Expand Down Expand Up @@ -150,6 +155,8 @@ test("binding normalization", () => {
},
);

new StringResolverPlugin().apply(view);

const resolved = view.update();
let barUpdate = view.update(new Set([parseBinding("boo")]));

Expand Down Expand Up @@ -197,6 +204,8 @@ test("hardcore immutability", () => {
},
);

new StringResolverPlugin().apply(view);

const resolved = view.update();

let barUpdate = view.update(new Set([parseBinding("boo")]));
Expand Down Expand Up @@ -246,6 +255,8 @@ test("should only update if data is used in view", () => {
},
);

new StringResolverPlugin().apply(view);

const hook = vitest.fn();
view.hooks.onUpdate.tap("update", hook);

Expand Down
33 changes: 32 additions & 1 deletion core/player/src/view/__tests__/view.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { LocalModel, withParser } from "../../data";
import { ExpressionEvaluator } from "../../expressions";
import { BindingParser } from "../../binding";
import { SchemaController } from "../../schema";
import { ViewInstance } from "..";
import {
StringResolverPlugin,
SwitchPlugin,
ViewInstance,
toNodeResolveOptions,
} from "..";

const parseBinding = new BindingParser().parse;

Expand Down Expand Up @@ -52,6 +57,10 @@ describe("view", () => {
},
);

const pluginOptions = toNodeResolveOptions(view.resolverOptions);
new SwitchPlugin(pluginOptions).apply(view);
new StringResolverPlugin().apply(view);

const resolved = view.update();

expect(resolved).toStrictEqual({
Expand Down Expand Up @@ -110,6 +119,10 @@ describe("view", () => {
},
);

const pluginOptions = toNodeResolveOptions(view.resolverOptions);
new SwitchPlugin(pluginOptions).apply(view);
new StringResolverPlugin().apply(view);

const resolved = view.update();

expect(resolved).toStrictEqual({
Expand Down Expand Up @@ -165,6 +178,10 @@ describe("view", () => {
},
);

const pluginOptions = toNodeResolveOptions(view.resolverOptions);
new SwitchPlugin(pluginOptions).apply(view);
new StringResolverPlugin().apply(view);

const resolved = view.update();

expect(resolved).toStrictEqual({
Expand Down Expand Up @@ -222,6 +239,10 @@ describe("view", () => {
},
);

const pluginOptions = toNodeResolveOptions(view.resolverOptions);
new SwitchPlugin(pluginOptions).apply(view);
new StringResolverPlugin().apply(view);

const resolved = view.update();

expect(resolved).toStrictEqual({
Expand Down Expand Up @@ -306,6 +327,10 @@ describe("view", () => {
},
);

const pluginOptions = toNodeResolveOptions(view.resolverOptions);
new SwitchPlugin(pluginOptions).apply(view);
new StringResolverPlugin().apply(view);

const resolved = view.update();

expect(resolved).toStrictEqual({
Expand Down Expand Up @@ -400,6 +425,10 @@ describe("view", () => {
},
);

const pluginOptions = toNodeResolveOptions(view.resolverOptions);
new SwitchPlugin(pluginOptions).apply(view);
new StringResolverPlugin().apply(view);

const resolved = view.update();

expect(resolved).toStrictEqual({
Expand Down Expand Up @@ -462,6 +491,8 @@ describe("view", () => {
},
);

new StringResolverPlugin().apply(view);

const resolved = view.update();

expect(resolved).toStrictEqual({
Expand Down
42 changes: 40 additions & 2 deletions core/player/src/view/parser/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { setIn } from "timm";
import { omit, setIn } from "timm";
import { SyncBailHook, SyncWaterfallHook } from "tapable-ts";
import type { Template } from "@player-ui/types";
import type { Node, AnyAssetType } from "./types";
import type { AnyAssetType, Node } from "./types";
import { NodeType } from "./types";
import { getNodeID, hasAsync } from "./utils";

export * from "./types";
export * from "./utils";

export const EMPTY_NODE: Node.Empty = {
type: NodeType.Empty,
Expand Down Expand Up @@ -73,6 +75,27 @@ export class Parser {
return viewNode as Node.View;
}

private parseAsync(
obj: object,
type: Node.ChildrenTypes,
options: ParseObjectOptions,
): Node.Node | null {
const parsedAsync = this.parseObject(omit(obj, "async"), type, options);
const parsedNodeId = getNodeID(parsedAsync);
if (parsedAsync !== null && parsedNodeId) {
return this.createASTNode(
{
id: parsedNodeId,
type: NodeType.Async,
value: parsedAsync,
},
obj,
);
}

return null;
}

public createASTNode(node: Node.Node | null, value: any): Node.Node | null {
const tapped = this.hooks.onCreateASTNode.call(node, value);

Expand Down Expand Up @@ -117,6 +140,9 @@ export class Parser {
}
}

/**
*
*/
const parseLocalObject = (
currentValue: any,
objToParse: unknown,
Expand Down Expand Up @@ -253,6 +279,18 @@ export class Parser {
],
};
}
} else if (localValue && hasAsync(localValue)) {
const localAsync = this.parseAsync(
localValue,
NodeType.Value,
options,
);
if (localAsync) {
children.push({
path: [...path, localKey],
value: localAsync,
});
}
} else if (localValue && Array.isArray(localValue)) {
const childValues = localValue
.map((childVal) =>
Expand Down
9 changes: 9 additions & 0 deletions core/player/src/view/parser/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export enum NodeType {
Value = "value",
MultiNode = "multi-node",
Switch = "switch",
Async = "async",
Unknown = "unknown",
Empty = "empty",
}
Expand Down Expand Up @@ -110,6 +111,13 @@ export declare namespace Node {
value: Value;
}

export interface Async extends Base<NodeType.Async> {
/** The unique id of the node */
id: string;
/** The value representing the node */
value: Node;
}

export interface PluginOptions {
/** A list of plugins */
plugins?: {
Expand All @@ -136,6 +144,7 @@ export declare namespace Node {
| View
| MultiNode
| Switch
| Async
| Unknown
| Empty;
}
21 changes: 21 additions & 0 deletions core/player/src/view/parser/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Node } from "./types";

/** Check to see if the object contains async */
export function hasAsync(obj: object): boolean {
return Object.prototype.hasOwnProperty.call(obj, "async");
}

/** Get the ID of the Node if there is one */
export function getNodeID(node?: Node.Node | null): string | undefined {
if (!node) {
return;
}

if (
"value" in node &&
typeof node.value === "object" &&
typeof node.value?.id === "string"
) {
return node.value.id;
}
}
Loading