Skip to content

Commit f77b23f

Browse files
authored
Merge pull request #73 from bcdev/forman-33-actions_tests
Actions test #1
2 parents a5f6b52 + d597c8e commit f77b23f

File tree

11 files changed

+307
-103
lines changed

11 files changed

+307
-103
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import type { ComponentType, FC } from "react";
2+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
3+
import { configureFramework, resolvePlugin } from "./configureFramework";
4+
import { store } from "@/store";
5+
import { registry } from "@/components/registry";
6+
import type { HostStore } from "@/types/state/host";
7+
import type { Plugin } from "@/types/state/plugin";
8+
import type { ComponentProps } from "@/components/Component";
9+
10+
function getComponents(): [string, ComponentType<ComponentProps>][] {
11+
interface DivProps extends ComponentProps {
12+
text: string;
13+
}
14+
const Div: FC<DivProps> = ({ text }) => <div>{text}</div>;
15+
return [
16+
["A", Div as FC<ComponentProps>],
17+
["B", Div as FC<ComponentProps>],
18+
];
19+
}
20+
21+
describe("configureFramework", () => {
22+
it("should accept no arg", () => {
23+
configureFramework();
24+
expect(store.getState().configuration).toEqual({});
25+
});
26+
27+
it("should accept empty arg", () => {
28+
configureFramework({});
29+
expect(store.getState().configuration).toEqual({});
30+
});
31+
32+
it("should enable logging", () => {
33+
configureFramework({
34+
logging: {
35+
enabled: true,
36+
},
37+
});
38+
expect(store.getState().configuration).toEqual({
39+
logging: { enabled: true },
40+
});
41+
});
42+
43+
it("should subscribe to host store", () => {
44+
const listeners = [];
45+
const hostStore: HostStore = {
46+
get: (_key: string) => null,
47+
subscribe: (l: () => void) => {
48+
listeners.push(l);
49+
},
50+
};
51+
configureFramework({
52+
hostStore,
53+
});
54+
expect(listeners.length).toBe(1);
55+
});
56+
57+
it("should install plugins", () => {
58+
expect(registry.types.length).toBe(0);
59+
configureFramework({
60+
plugins: [{ components: getComponents() }],
61+
});
62+
expect(registry.types.length).toBe(2);
63+
});
64+
});
65+
66+
describe("resolvePlugin", () => {
67+
beforeEach(() => {
68+
registry.clear();
69+
});
70+
71+
afterEach(() => {
72+
registry.clear();
73+
});
74+
75+
it("should resolve a object", async () => {
76+
const pluginObj: Plugin = { components: getComponents() };
77+
expect(registry.types.length).toBe(0);
78+
const result = await resolvePlugin(pluginObj);
79+
expect(result).toBe(pluginObj);
80+
expect(registry.types.length).toBe(2);
81+
});
82+
83+
it("should resolve a function", async () => {
84+
const pluginObj = { components: getComponents() };
85+
const pluginFunction = () => pluginObj;
86+
expect(registry.types.length).toBe(0);
87+
const result = await resolvePlugin(pluginFunction);
88+
expect(result).toBe(pluginObj);
89+
expect(registry.types.length).toBe(2);
90+
});
91+
92+
it("should resolve a promise", async () => {
93+
const pluginObj = { components: getComponents() };
94+
const pluginPromise = Promise.resolve(pluginObj);
95+
expect(registry.types.length).toBe(0);
96+
const result = await resolvePlugin(pluginPromise);
97+
expect(result).toBe(pluginObj);
98+
expect(registry.types.length).toBe(2);
99+
});
100+
101+
it("should resolve undefined", async () => {
102+
expect(registry.types.length).toBe(0);
103+
const result = await resolvePlugin(undefined as unknown as Plugin);
104+
expect(result).toBe(undefined);
105+
expect(registry.types.length).toBe(0);
106+
});
107+
});

chartlets.js/packages/lib/src/actions/configureFramework.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import { store } from "@/store";
2+
import type { FrameworkOptions } from "@/types/state/options";
23
import type {
34
ComponentRegistration,
4-
FrameworkOptions,
5+
Plugin,
56
PluginLike,
6-
} from "@/types/state/options";
7+
} from "@/types/state/plugin";
78
import { registry } from "@/components/registry";
89
import { isPromise } from "@/utils/isPromise";
910
import { isFunction } from "@/utils/isFunction";
1011
import { isObject } from "@/utils/isObject";
1112
import { handleHostStoreChange } from "./handleHostStoreChange";
1213
import { configureLogging } from "./helpers/configureLogging";
1314

14-
export function configureFramework(options: FrameworkOptions) {
15+
export function configureFramework(options?: FrameworkOptions) {
16+
options = options || {};
1517
if (options.logging) {
1618
configureLogging(options.logging);
1719
}
@@ -26,16 +28,18 @@ export function configureFramework(options: FrameworkOptions) {
2628
}
2729
}
2830

29-
function resolvePlugin(plugin: PluginLike) {
31+
export function resolvePlugin(plugin: PluginLike): Promise<Plugin | undefined> {
3032
if (isPromise<PluginLike>(plugin)) {
31-
plugin.then(resolvePlugin);
33+
return plugin.then(resolvePlugin);
3234
} else if (isFunction(plugin)) {
33-
resolvePlugin(plugin());
35+
return resolvePlugin(plugin());
3436
} else if (isObject(plugin) && plugin.components) {
3537
(plugin.components as ComponentRegistration[]).forEach(
3638
([name, component]) => {
3739
registry.register(name, component);
3840
},
3941
);
42+
return Promise.resolve(plugin as Plugin);
4043
}
44+
return Promise.resolve(undefined);
4145
}

chartlets.js/packages/lib/src/actions/handleHostStoreChange.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { store } from "@/store";
21
import type {
32
CallbackRef,
43
CallbackRequest,
@@ -10,7 +9,8 @@ import { getInputValues } from "@/actions/helpers/getInputValues";
109
import { formatObjPath } from "@/utils/objPath";
1110
import { invokeCallbacks } from "@/actions/helpers/invokeCallbacks";
1211
import type { ContributionState } from "@/types/state/contribution";
13-
import type { HostStore } from "@/types/state/options";
12+
import type { HostStore } from "@/types/state/host";
13+
import { store } from "@/store";
1414

1515
/**
1616
* A reference to a property of an input of a callback of a contribution.
@@ -23,12 +23,16 @@ export interface PropertyRef extends ContribRef, CallbackRef, InputRef {
2323
export function handleHostStoreChange() {
2424
const { extensions, configuration, contributionsRecord } = store.getState();
2525
const { hostStore } = configuration;
26-
if (!hostStore || extensions.length === 0) {
27-
// Exit if no host store configured or
28-
// there are no extensions (yet)
26+
if (!hostStore) {
27+
// Exit if no host store configured.
28+
// Actually, we should not come here.
2929
return;
3030
}
3131
synchronizeThemeMode(hostStore);
32+
if (extensions.length === 0) {
33+
// Exit if there are no extensions (yet)
34+
return;
35+
}
3236
const propertyRefs = getHostStorePropertyRefs();
3337
if (!propertyRefs || propertyRefs.length === 0) {
3438
// Exit if there are is nothing to be changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { describe, it, expect, beforeEach } from "vitest";
2+
import { store } from "@/store";
3+
import { handleHostStoreChange } from "./handleHostStoreChange";
4+
5+
describe("handleHostStoreChange", () => {
6+
let listeners: (() => void)[] = [];
7+
let hostState: Record<string, unknown> = {};
8+
const hostStore = {
9+
get: (key: string) => hostState[key],
10+
set: (key: string, value: unknown) => {
11+
hostState = { ...hostState, [key]: value };
12+
listeners.forEach((l) => void l());
13+
},
14+
subscribe: (_l: () => void) => {
15+
listeners.push(_l);
16+
},
17+
};
18+
19+
beforeEach(() => {
20+
listeners = [];
21+
hostState = {};
22+
});
23+
24+
it("should do nothing without host store", () => {
25+
store.setState({ configuration: {} });
26+
const oldState = store.getState();
27+
handleHostStoreChange();
28+
const newState = store.getState();
29+
expect(newState).toBe(oldState);
30+
expect(newState).toEqual(oldState);
31+
});
32+
33+
it("should synchronize theme mode", () => {
34+
store.setState({ configuration: { hostStore } });
35+
expect(store.getState().themeMode).toBeUndefined();
36+
hostStore.set("themeMode", "light");
37+
handleHostStoreChange();
38+
expect(store.getState().themeMode).toEqual("light");
39+
});
40+
41+
it("should generate callback requests", () => {
42+
const extensions = [{ name: "e0", version: "0", contributes: ["panels"] }];
43+
store.setState({
44+
configuration: { hostStore },
45+
extensions,
46+
contributionsResult: {
47+
status: "ok",
48+
data: {
49+
extensions,
50+
contributions: {
51+
panels: [
52+
{
53+
name: "p0",
54+
extension: "e0",
55+
layout: {
56+
function: {
57+
name: "layout",
58+
parameters: [],
59+
return: {},
60+
},
61+
inputs: [],
62+
outputs: [],
63+
},
64+
callbacks: [
65+
{
66+
function: {
67+
name: "callback",
68+
parameters: [],
69+
return: {},
70+
},
71+
inputs: [{ id: "@app", property: "variableName" }],
72+
outputs: [{ id: "select", property: "value" }],
73+
},
74+
],
75+
initialState: {},
76+
},
77+
],
78+
},
79+
},
80+
},
81+
});
82+
hostStore.set("variableName", "CHL");
83+
handleHostStoreChange();
84+
});
85+
});

chartlets.js/packages/lib/src/actions/helpers/applyStateChangeRequests.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@ import {
1515
normalizeObjPath,
1616
setValue,
1717
} from "@/utils/objPath";
18-
import {
19-
isMutableHostStore,
20-
type MutableHostStore,
21-
} from "@/types/state/options";
18+
import { isMutableHostStore, type MutableHostStore } from "@/types/state/host";
2219
import {
2320
isHostChannel,
2421
isComponentChannel,

chartlets.js/packages/lib/src/actions/helpers/getInputValues.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
} from "@/types/state/component";
1313
import { formatObjPath, getValue, type ObjPathLike } from "@/utils/objPath";
1414
import { isObject } from "@/utils/isObject";
15-
import type { HostStore } from "@/types/state/options";
15+
import type { HostStore } from "@/types/state/host";
1616

1717
export function getInputValues(
1818
inputs: Input[],

chartlets.js/packages/lib/src/index.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,6 @@ export {
2929
} from "@/hooks";
3030

3131
// Application interface
32-
export type {
33-
FrameworkOptions,
34-
HostStore,
35-
MutableHostStore,
36-
Plugin,
37-
PluginLike,
38-
} from "@/types/state/options";
32+
export type { HostStore, MutableHostStore } from "@/types/state/host";
33+
export type { Plugin, PluginLike } from "@/types/state/plugin";
34+
export type { FrameworkOptions } from "@/types/state/options";

chartlets.js/packages/lib/src/types/state/options.test.ts renamed to chartlets.js/packages/lib/src/types/state/host.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
type MutableHostStore,
55
isHostStore,
66
isMutableHostStore,
7-
} from "./options";
7+
} from "./host";
88

99
const hostStore: HostStore = {
1010
get: (name: string) => name,
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { isObject } from "@/utils/isObject";
2+
import { isFunction } from "@/utils/isFunction";
3+
4+
/**
5+
* The host store represents an interface to the state of
6+
* the application that is using Chartlets.
7+
*/
8+
export interface HostStore {
9+
/**
10+
* Let Chartlets listen to changes in the host store that may
11+
* cause different values to be returned from the `get()` method.
12+
*
13+
* @param listener A listener that is called when the
14+
* host store changes
15+
*/
16+
subscribe: (listener: () => void) => void;
17+
18+
/**
19+
* Get a property value from the host state.
20+
*
21+
* @param property The property name.
22+
* @returns The property value.
23+
*/
24+
get: (property: string) => unknown;
25+
26+
/**
27+
* **UNSTABLE API**
28+
*
29+
* Set a property value in the host state.
30+
*
31+
* @param property The property name.
32+
* @param value The new property value.
33+
*/
34+
set?: (property: string, value: unknown) => void;
35+
}
36+
37+
/**
38+
* A mutable host store implements the `set()` method.
39+
*/
40+
export interface MutableHostStore extends HostStore {
41+
/**
42+
* **UNSTABLE API**
43+
*
44+
* Set a property value in the host state.
45+
*
46+
* @param property The property name.
47+
* @param value The new property value.
48+
*/
49+
set: (property: string, value: unknown) => void;
50+
}
51+
52+
export function isHostStore(value: unknown): value is HostStore {
53+
return (
54+
isObject(value) && isFunction(value.get) && isFunction(value.subscribe)
55+
);
56+
}
57+
58+
export function isMutableHostStore(value: unknown): value is MutableHostStore {
59+
return isHostStore(value) && isFunction(value.set);
60+
}

0 commit comments

Comments
 (0)