@player-ui/player
Version:
313 lines (277 loc) • 6.88 kB
text/typescript
import { test, vitest, expect } from "vitest";
import type { FlowController } from "../controllers";
import type { DataController } from "..";
import { Player } from "..";
import type { InProgressState } from "../types";
test("transitions on action nodes", async () => {
const player = new Player();
player.start({
id: "test-flow",
data: {
my: {
puppy: "Ginger",
},
},
navigation: {
BEGIN: "FLOW_1",
FLOW_1: {
startState: "ACTION_1",
ACTION_1: {
state_type: "ACTION",
exp: "{{my.puppy}}",
transitions: {
Ginger: "EXTERNAL_1",
},
},
EXTERNAL_1: {
state_type: "EXTERNAL",
ref: "view_1",
param: {
best: "{{my.puppy}}",
},
transitions: {},
},
},
},
});
await vitest.waitFor(() =>
expect(player.getState().status).toBe("in-progress"),
);
const state = player.getState();
const currentState = (state as InProgressState).controllers.flow.current
?.currentState;
expect(currentState?.name).toBe("EXTERNAL_1");
expect(currentState?.value).toStrictEqual({
state_type: "EXTERNAL",
ref: "view_1",
param: {
best: "Ginger",
},
transitions: {},
});
});
test("resolves data when transitioning", async () => {
const player = new Player();
let flowController: FlowController | undefined;
player.hooks.flowController.tap("test", (fc) => {
flowController = fc;
});
const flowResponse = player.start({
id: "test-flow",
views: [
{ id: "view-1", type: "view" },
{ id: "view-2", type: "view" },
],
data: {
viewNum: 2,
outcomeData: "not good",
},
navigation: {
BEGIN: "FLOW_1",
FLOW_1: {
startState: "VIEW_1",
VIEW_1: {
state_type: "VIEW",
ref: "view-1",
transitions: {
Next: "VIEW_{{viewNum}}",
},
},
VIEW_2: {
state_type: "VIEW",
ref: "view-{{viewNum}}",
transitions: {
"*": "END",
},
},
END: {
state_type: "END",
outcome: "{{outcomeData}}",
},
},
},
});
/** A helper to get the current view */
const getView = () =>
(player.getState() as InProgressState).controllers.view.currentView
?.lastUpdate;
expect(getView()).toStrictEqual({
id: "view-1",
type: "view",
});
flowController?.transition("Next");
expect(getView()).toStrictEqual({
id: "view-2",
type: "view",
});
flowController?.transition("Next");
expect((await flowResponse).endState).toStrictEqual({
state_type: "END",
outcome: "not good",
});
});
test("resolves dynamic view ids", () => {
const player = new Player();
player.start({
id: "test-flow",
views: [{ id: "view-1-{{name}}", type: "view" }],
data: {
viewNum: 1,
name: "adam",
outcomeData: "not good",
},
navigation: {
BEGIN: "FLOW_1",
FLOW_1: {
startState: "VIEW_1",
VIEW_1: {
state_type: "VIEW",
ref: "view-1-adam",
transitions: {
Next: "END",
},
},
END: {
state_type: "END",
outcome: "{{outcomeData}}",
},
},
},
});
const state = player.getState();
expect(state.status).toBe("in-progress");
expect(
(state as InProgressState).controllers.view.currentView?.lastUpdate?.id,
).toBe("view-1-adam");
});
test("resolves data when completing", async () => {
const player = new Player();
let flowController: FlowController | undefined;
let dataController: DataController | undefined;
player.hooks.flowController.tap("test", (fc) => {
flowController = fc;
});
player.hooks.dataController.tap("test", (dc) => {
dataController = dc;
});
const flowResponse = player.start({
id: "test-flow",
views: [
{ id: "view-1", type: "view" },
{ id: "view-2", type: "view" },
],
data: {
viewNum: 2,
outcomeData: "not good",
},
navigation: {
BEGIN: "FLOW_1",
FLOW_1: {
startState: "VIEW_1",
VIEW_1: {
state_type: "VIEW",
ref: "view-1",
transitions: {
Next: "VIEW_{{viewNum}}",
},
},
VIEW_2: {
state_type: "VIEW",
ref: "view-{{viewNum}}",
transitions: {
"*": "END",
},
},
END: {
state_type: "END",
outcome: "{{outcomeData}}",
},
},
},
});
/** A helper to get the current view */
const getView = () =>
(player.getState() as InProgressState).controllers.view.currentView
?.lastUpdate;
expect(getView()).toStrictEqual({
id: "view-1",
type: "view",
});
flowController?.transition("Next");
dataController?.set({ testData: "testValue" });
expect(getView()).toStrictEqual({
id: "view-2",
type: "view",
});
flowController?.transition("Next");
const result = await flowResponse;
expect(result.data).toStrictEqual({
viewNum: 2,
outcomeData: "not good",
testData: "testValue",
});
});
test("resolves param on end nodes", async () => {
const player = new Player();
const flowResponse = player.start({
id: "test-flow",
views: [
{ id: "view-1", type: "view" },
{ id: "view-2", type: "view" },
],
data: {
cat: {
name: "Sam",
},
},
navigation: {
BEGIN: "FLOW_1",
FLOW_1: {
startState: "VIEW_1",
VIEW_1: {
state_type: "VIEW",
ref: "view-1",
transitions: {
Next: "ACTION_1",
},
},
ACTION_1: {
state_type: "ACTION",
exp: '{{cat.name}} = "FRODO"',
transitions: {
"*": "END",
},
},
END: {
state_type: "END",
outcome: "favoritePet",
param: "{{cat.name}}",
},
},
},
});
(player.getState() as InProgressState).controllers.flow.transition("Next");
expect((await flowResponse).endState).toStrictEqual({
state_type: "END",
outcome: "favoritePet",
param: "FRODO",
});
});
test("works with iffe flows", async () => {
const player = new Player();
const flowResponse = player.start({
id: "first-end-flow",
navigation: {
BEGIN: "DoneWithTopicFlow",
DoneWithTopicFlow: {
startState: "END_done",
END_before_topic: { state_type: "END", outcome: "BACK" },
END_done: { state_type: "END", outcome: "doneWithTopic" },
},
},
});
expect((await flowResponse).endState).toStrictEqual({
state_type: "END",
outcome: "doneWithTopic",
});
});