;
51 |
52 | export interface CreateAsyncActionReturnType {
53 | pending: AsyncActionStatusesType;
54 | fulfilled: AsyncActionStatusesType;
55 | rejected: AsyncActionStatusesType;
56 | handler: CreateAsyncActionProp;
57 | }
58 |
59 | export type AsyncActionResponse = Promise<{
60 | state: P;
61 | data: T;
62 | error: Error | null;
63 | status: AsyncActionStatusesType;
64 | }>;
65 |
66 | export const AsyncActionStatuses = {
67 | PENDING: "PENDING",
68 | FULFILLED: "FULFILLED",
69 | REJECTED: "REJECTED",
70 | } as const;
71 |
72 | export type AsyncActionStatusesType =
73 | (typeof AsyncActionStatuses)[keyof typeof AsyncActionStatuses];
74 |
--------------------------------------------------------------------------------
/src/hooks/types.ts:
--------------------------------------------------------------------------------
1 | import { type AsyncActionStatusesType } from '../helpers/types.js'
2 |
3 | export type Actions = Record void>
4 |
5 | export type AsyncActions = Record Promise<{
6 | state: T,
7 | data: any | null,
8 | error: Error | null,
9 | status: AsyncActionStatusesType
10 | }>>
11 |
12 | export type Operations = Record P>
13 |
--------------------------------------------------------------------------------
/src/hooks/useAction.ts:
--------------------------------------------------------------------------------
1 | import { type Actions } from './types.js'
2 | import useActions from './useActions.js'
3 |
4 | const useAction = (signalName: string, action: string) => {
5 | if (!signalName || typeof signalName !== 'string') { throw new Error('Provide a signalName as a first argument of useAction') }
6 |
7 | if (!action || typeof action !== 'string') { throw new Error('Provide an action as second argument of useAction') }
8 |
9 | const actions = useActions(signalName, action)
10 |
11 | return Object.values(actions)[0]
12 | }
13 |
14 | export default useAction
15 |
--------------------------------------------------------------------------------
/src/hooks/useActions.ts:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react'
2 | import GXContext from '../contexts/index.js'
3 | import { type GXActionType } from '../contexts/types.js'
4 | import { type Actions } from './types.js'
5 |
6 | const useActions = (
7 | signalName: string,
8 | ...actions: string[]
9 | ): T => {
10 | if (!signalName || typeof signalName !== 'string') {
11 | throw new Error('Provide a signalName as first argument of useActions')
12 | }
13 |
14 | // Get Global Context
15 | const { signals, dispatch } = useContext(GXContext)
16 |
17 | // Some handlers
18 |
19 | /**
20 | * Get actions of a signal
21 | * @param signalName
22 | * @returns
23 | */
24 | const handleGetActions = (signalName: string) => {
25 | const signal = signals.find((signal) => signal.name === signalName)
26 |
27 | if (signal) {
28 | if (!actions || actions.length === 0) return signal.actions
29 |
30 | const filteredActions: Array> = []
31 |
32 | for (const action of actions) {
33 | const actionName = `${signalName}/${action}`
34 |
35 | const retrievedAction = signal.actions.find(
36 | (act) => act.type === actionName
37 | )
38 |
39 | if (retrievedAction) filteredActions.push(retrievedAction)
40 | else throw new Error(`Action ${actionName} not found`)
41 | }
42 |
43 | return filteredActions
44 | } else throw new Error(`Signal ${signalName} not found`)
45 | }
46 |
47 | const handleFormatActions = (): T => {
48 | // Get actions
49 | const nonFormattedActions = handleGetActions(signalName)
50 |
51 | // Formatted actions
52 | const formattedActions = {} as any
53 |
54 | for (const action of nonFormattedActions) {
55 | // Get action name
56 | const actionName = action.type.split('/')[1]
57 |
58 | formattedActions[actionName] = (payload?: any) => {
59 | dispatch({
60 | type: action.type,
61 | isAsync: false,
62 | payload
63 | })
64 | }
65 | }
66 |
67 | return formattedActions
68 | }
69 |
70 | return handleFormatActions()
71 | }
72 |
73 | export default useActions
74 |
--------------------------------------------------------------------------------
/src/hooks/useAllSignals.ts:
--------------------------------------------------------------------------------
1 | import { useContext } from "react"
2 | import GXContext from "../contexts/index.js"
3 |
4 | export default function useAllSignals() {
5 | // Global state that manage all signals
6 | const { signals } = useContext(GXContext)
7 |
8 | return signals
9 | }
--------------------------------------------------------------------------------
/src/hooks/useAsyncActions.ts:
--------------------------------------------------------------------------------
1 | import {
2 | useCallback,
3 | useContext,
4 | useEffect,
5 | useMemo,
6 | useRef,
7 | useState,
8 | } from "react";
9 | import { type AsyncActions } from "./types.js";
10 | import GXContext from "../contexts/index.js";
11 | import { type GXAsyncActionType } from "../contexts/types.js";
12 | import { AsyncActionStatuses } from "../helpers/types.js";
13 | import { BuilderCase } from "../interfaces/builderCase.js";
14 |
15 | const useAsyncActions = >(
16 | signalName: string,
17 | ...actions: string[]
18 | ) => {
19 | if (!signalName || typeof signalName !== "string") {
20 | throw new Error(
21 | "Provide a signalName as first argument of useAsyncActions"
22 | );
23 | }
24 |
25 | // Get Global Context
26 | const { signals, asyncDispatch } = useContext(GXContext);
27 |
28 | // Get state from signals
29 | // Extract type from P generic type
30 | type StateType = P extends AsyncActions ? U : any;
31 |
32 | const state = useMemo(() => {
33 | const signal = signals.find((signal) => signal.name === signalName);
34 |
35 | if (signal) return signal.state;
36 | else throw new Error(`Signal ${signalName} not found`);
37 | }, [signals]);
38 |
39 | // Refs
40 | // Define a ref to block the execution of async action callback twice
41 | const isAsyncActionCallbackRunning = useRef<{ [key: string]: Boolean }>({});
42 |
43 | // Async action callback
44 | const asyncActionCallback = useRef(
45 | async (action: GXAsyncActionType, payload?: any) => {
46 | // Prevent the execution of async action callback twice
47 | if (isAsyncActionCallbackRunning.current[action.type])
48 | return new Promise((resolve) => {
49 | resolve({
50 | status: AsyncActionStatuses.PENDING,
51 | state,
52 | error: null,
53 | data: null,
54 | });
55 | });
56 |
57 | // Set the ref to true
58 | isAsyncActionCallbackRunning.current[action.type] = true;
59 |
60 | // Dispatch pending action
61 | asyncDispatch({
62 | type: action.type,
63 | isAsync: true,
64 | status: AsyncActionStatuses.PENDING,
65 | });
66 |
67 | try {
68 | // Execute async action
69 | const response = await (
70 | action.steps as BuilderCase
71 | ).asyncAction.handler(payload);
72 |
73 | // Dispatch fulfilled action
74 | const data = asyncDispatch({
75 | type: action.type,
76 | isAsync: true,
77 | status: AsyncActionStatuses.FULFILLED,
78 | payload: response,
79 | });
80 |
81 | return {
82 | state: data,
83 | data: response,
84 | error: null,
85 | status: AsyncActionStatuses.FULFILLED,
86 | };
87 | } catch (error) {
88 | // Dispatch rejected action
89 | const data = asyncDispatch({
90 | type: action.type,
91 | isAsync: true,
92 | status: AsyncActionStatuses.REJECTED,
93 | payload: error,
94 | });
95 |
96 | return {
97 | state: data,
98 | data: null,
99 | error: new Error(error),
100 | status: AsyncActionStatuses.REJECTED,
101 | };
102 | } finally {
103 | // Set the ref to false
104 | isAsyncActionCallbackRunning.current[action.type] = false;
105 | }
106 | }
107 | );
108 |
109 | // Some handlers
110 |
111 | /**
112 | * Get async actions of a signal
113 | * @param signalName
114 | * @returns
115 | */
116 | const handleGetAsyncActions = (signalName: string) => {
117 | const signal = signals.find((signal) => signal.name === signalName);
118 |
119 | if (signal) {
120 | if (!actions || actions.length === 0) return signal.asyncActions || [];
121 |
122 | const filteredActions: Array> = [];
123 |
124 | for (const action of actions) {
125 | const actionName = `${signalName}/${action}`;
126 |
127 | const retrievedAction = signal.asyncActions.find(
128 | (act) => act.type === actionName
129 | );
130 |
131 | if (retrievedAction) filteredActions.push(retrievedAction);
132 | else throw new Error(`Async Action ${actionName} not found`);
133 | }
134 |
135 | return filteredActions;
136 | } else throw new Error(`Signal ${signalName} not found`);
137 | };
138 |
139 | /**
140 | * Format async actions
141 | * @returns
142 | */
143 | const handleFormatAsyncActions = (): P => {
144 | // Get actions
145 | const nonFormattedActions = handleGetAsyncActions(signalName);
146 |
147 | // Formatted actions
148 | const formattedActions = nonFormattedActions.map((action) => {
149 | // Get action name
150 | const actionName = action.type.split("/")[1];
151 |
152 | return [
153 | actionName,
154 | async (payload?: any) => {
155 | return asyncActionCallback.current(action, payload);
156 | },
157 | ];
158 | });
159 |
160 | return Object.fromEntries(formattedActions);
161 | };
162 |
163 | return handleFormatAsyncActions();
164 | };
165 |
166 | export default useAsyncActions;
167 |
--------------------------------------------------------------------------------
/src/hooks/useOperations.ts:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react'
2 | import GXContext from '../contexts/index.js'
3 | import { type Operations } from './types.js'
4 |
5 | const useOperations = (signalName: string) => {
6 | // Get Global Context
7 | const { signals } = useContext(GXContext)
8 |
9 | if (!signalName || typeof signalName !== 'string') {
10 | throw new Error(
11 | 'Provide a signalName as a first argument of useOperations'
12 | )
13 | }
14 |
15 | const handleFormatOperations = (): T => {
16 | const signal = signals.find((signal) => signal.name === signalName)
17 |
18 | if (!signal) throw new Error(`Signal ${signalName} not found`)
19 |
20 | // Get actions
21 | const nonFormattedOperations = signal.operations
22 |
23 | // Formatted actions
24 | const formattedOperations = {} as any
25 |
26 | for (const operation of nonFormattedOperations) {
27 | // Get action name
28 | const operationName = operation.type.split('/')[1]
29 |
30 | formattedOperations[operationName] = (payload?: any) => {
31 | return operation.handler(signal.state, payload)
32 | }
33 | }
34 |
35 | // return formattedOperations;
36 |
37 | return formattedOperations
38 | }
39 |
40 | return handleFormatOperations()
41 | }
42 |
43 | // Définir un type générique pour représenter une fonction
44 | // type FunctionType any> = T;
45 |
46 | // Définir un type générique pour représenter un objet contenant des fonctions
47 | // type FunctionObject = {
48 | // [K in keyof T]: T[K] extends Function ? FunctionType : never;
49 | // };
50 |
51 | // type FunctionObject = {
52 | // [K in keyof T]: T[K] extends (payload: infer Arg) => infer R
53 | // ? (payload: Arg) => R
54 | // : (payload?: any) => any;
55 | // };
56 |
57 | // // Utilisation d'une fonction auxiliaire pour extraire le type du second paramètre
58 | // type SecondParamType = T extends (a: any, b: infer P) => any ? P : any;
59 |
60 | // // Utilisation d'une fonction auxiliaire pour extraire le type de retour d'une fonction
61 | // type ReturnTypeFunc = T extends (...args: any[]) => infer R ? R : any;
62 |
63 | export default useOperations
64 |
--------------------------------------------------------------------------------
/src/hooks/useSignal.ts:
--------------------------------------------------------------------------------
1 | import { useContext, useMemo } from 'react'
2 | import GXContext from '../contexts/index.js'
3 |
4 | const useSignal = (signalName: string) => {
5 | const { signals } = useContext(GXContext)
6 | const memoizedSignals = useMemo(() => signals, [signals])
7 |
8 | /**
9 | * Get state of a signal base on its name
10 | * @param signalName
11 | * @returns
12 | */
13 | const handleGetSignalState = (signalName: string): T => {
14 | const signal = memoizedSignals.find(signal => signal.name === signalName)
15 |
16 | if (signal) {
17 | return signal.state
18 | }
19 |
20 | // Throw error if signal not found
21 | throw new Error(`Signal ${signalName} not found`)
22 | }
23 |
24 | return handleGetSignalState(signalName)
25 | }
26 |
27 | export default useSignal
28 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | // Provider
2 | import GXProvider from "./providers/index.js";
3 |
4 | // Constants
5 | import { AsyncActionStatuses } from "./helpers/types.js";
6 |
7 | // Types
8 | import { type AsyncActionResponse } from "./helpers/types.js";
9 |
10 | // Helpers functions
11 | import createSignal from "./helpers/createSignal.js";
12 | import createStore from "./helpers/createStore.js";
13 | import createAsyncAction from "./helpers/createAsyncAction.js";
14 |
15 | // Hooks
16 | import useAction from "./hooks/useAction.js";
17 | import useActions from "./hooks/useActions.js";
18 | import useAsyncActions from "./hooks/useAsyncActions.js";
19 | import useAllSignals from "./hooks/useAllSignals.js";
20 | import useSignal from "./hooks/useSignal.js";
21 | import useOperations from "./hooks/useOperations.js";
22 |
23 | export default GXProvider;
24 |
25 | export {
26 | createSignal,
27 | createStore,
28 | createAsyncAction,
29 | useAction,
30 | useActions,
31 | useAsyncActions,
32 | useAllSignals,
33 | useSignal,
34 | useOperations,
35 | AsyncActionStatuses,
36 | AsyncActionResponse
37 | };
38 |
39 | // "build": "tsc && npx babel dist --out-dir cjs --extensions '.js' --source-maps inline --copy-files",
40 |
--------------------------------------------------------------------------------
/src/interfaces/builder.ts:
--------------------------------------------------------------------------------
1 | import { type CreateAsyncActionReturnType } from "../helpers/types.js";
2 | import type IBuilderCase from "./builderCase.js";
3 | import { BuilderCase } from "./builderCase.js";
4 |
5 | export default interface IBuilder {
6 | use: (asyncAction: CreateAsyncActionReturnType) => IBuilderCase;
7 | }
8 |
9 | /**
10 | * @class Builder
11 | * @implements IBuilder
12 | * @description
13 | * Builder class to initialize a new builder case instance and return it in order to
14 | * chain the builder case methods, or onPending, onFulfilled, onRejected methods to define
15 | * the async action steps.
16 | */
17 | export class Builder implements IBuilder {
18 | /**
19 | * This method takes an async action object and assign it to the builder case instance
20 | * @param asyncAction An async action object
21 | * @returns IBuilderCase
22 | */
23 | use(asyncAction: CreateAsyncActionReturnType): IBuilderCase {
24 | const builderCase = new BuilderCase();
25 |
26 | builderCase.asyncAction = asyncAction;
27 | builderCase.cases = [];
28 |
29 | return builderCase;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/interfaces/builderCase.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AsyncActionStatuses,
3 | type AsyncActionStatusesType,
4 | type CreateAsyncActionReturnType
5 | } from '../helpers/types.js'
6 |
7 | /**
8 | * Interface for builder case
9 | */
10 | export default interface IBuilderCase {
11 | case: (
12 | status: AsyncActionStatusesType,
13 | handler: (state: T, payload?: P) => T
14 | ) => IBuilderCase
15 |
16 | onPending: (handler: (state: T, payload?: P) => T) => IBuilderCase
17 |
18 | onFulfilled: (handler: (state: T, payload?: P) => T) => IBuilderCase
19 |
20 | onRejected: (handler: (state: T, payload?: P) => T) => IBuilderCase
21 | }
22 |
23 | /**
24 | * Builder case class for managing different cases of the asynchronous task
25 | * @param _cases List of cases defined for a specific asynchronous task
26 | */
27 | export class BuilderCase implements IBuilderCase {
28 | private _cases: Array>
29 | private _asyncAction: CreateAsyncActionReturnType | undefined
30 |
31 | constructor () {
32 | this._cases = []
33 | this._asyncAction = undefined
34 | }
35 |
36 | // Getters
37 |
38 | /**
39 | * Get the list of cases
40 | */
41 | get cases () {
42 | return this._cases
43 | }
44 |
45 | /**
46 | * Get the async action
47 | */
48 | get asyncAction () {
49 | return this._asyncAction
50 | }
51 |
52 | // Setters
53 |
54 | /**
55 | * Update the async action
56 | * @param asyncAction Async Action value
57 | */
58 | set asyncAction (asyncAction: CreateAsyncActionReturnType) {
59 | this._asyncAction = asyncAction
60 | }
61 |
62 | /**
63 | * Update the cases
64 | */
65 | set cases (cases: Array>) {
66 | this._cases = cases
67 | }
68 |
69 | /**
70 | * Method that add a new case into the _cases list and return a new case builder object
71 | * @param status Status of the asynchronous task
72 | * @param handler Function that is executed depending on the specific status
73 | * @returns
74 | */
75 | case (
76 | status: AsyncActionStatusesType,
77 | handler: (state: T, payload?: P) => T
78 | ): IBuilderCase {
79 | this._cases.push({
80 | status,
81 | handler
82 | })
83 |
84 | return this
85 | }
86 |
87 | /**
88 | * Method that add a pending case into the _cases list and return a new case builder object
89 | * @param handler Function that is executed depending on the specific status
90 | * @returns
91 | */
92 | onPending (handler: (state: T, payload?: P) => T): IBuilderCase {
93 | return this.case(AsyncActionStatuses.PENDING, handler)
94 | }
95 |
96 | /**
97 | * Method that add a fulfilled case into the _cases list and return a new case builder object
98 | * @param handler Function that is executed depending on the specific status
99 | * @returns
100 | */
101 | onFulfilled (handler: (state: T, payload?: P) => T): IBuilderCase {
102 | return this.case(AsyncActionStatuses.FULFILLED, handler)
103 | }
104 |
105 | /**
106 | * Method that add a rejected case into the _cases list and return a new case builder object
107 | * @param handler Function that is executed depending on the specific status
108 | * @returns
109 | **/
110 | onRejected (handler: (state: T, payload?: P) => T): IBuilderCase {
111 | return this.case(AsyncActionStatuses.REJECTED, handler)
112 | }
113 | }
114 |
115 | /**
116 | * Case interface
117 | */
118 | export interface Case {
119 | status: AsyncActionStatusesType
120 | handler: (state: T, payload?: P) => T
121 | }
122 |
--------------------------------------------------------------------------------
/src/providers/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useReducer, useTransition } from "react";
2 | import GXContext from "../contexts/index.js";
3 | import { type GXAction, type GXProviderProps } from "./types.js";
4 | import gxReducer from "./reducer.js";
5 | import { type BuilderCase } from "../interfaces/builderCase.js";
6 |
7 | export default function GXProvider({ children, store }: GXProviderProps) {
8 | // Global state that manage all signals
9 | const [signals, dispatch] = useReducer(gxReducer, store.getSignals());
10 |
11 | // Wrap your dispatch function with useTransition
12 | const [, startTransition] = useTransition();
13 |
14 | // Your state management logic using useContext and useReducer
15 | const syncDispatch = (action: GXAction) => {
16 | startTransition(() => {
17 | dispatch(action);
18 | });
19 | };
20 |
21 | const asyncDispatch = useCallback((action: GXAction) => {
22 | const signalName = action.type.split("/")[0];
23 |
24 | const newState = signals.map(
25 | ({ name, operations, actions, asyncActions, state: prevState }) => {
26 | let state = prevState;
27 |
28 | // Capture the target signal (a state and a bunch of async actions) from the array of signals.
29 | // Capture the action from array of async actions (of the target signal).
30 | // Run the async action and update the signal state.
31 | if (name === signalName) {
32 | if (action.isAsync) {
33 | for (const { type, steps } of asyncActions) {
34 | if (type === action.type) {
35 | state = (steps as BuilderCase).cases
36 | .find((c) => c.status === action.status)
37 | .handler(state, action.payload);
38 | break;
39 | }
40 | }
41 | }
42 | }
43 |
44 | return {
45 | name,
46 | operations,
47 | state,
48 | actions,
49 | asyncActions,
50 | };
51 | }
52 | );
53 |
54 | // Find the new state of the target signal
55 | const signal = newState.find((signal) => signal.name === signalName);
56 |
57 | dispatch({
58 | type: action.type,
59 | isAsync: action.isAsync,
60 | status: action.status,
61 | payload: signal.state,
62 | });
63 |
64 | return signal.state
65 | }, []);
66 |
67 | // Context value
68 | const contextValue = {
69 | signals,
70 | dispatch: syncDispatch,
71 | asyncDispatch
72 | };
73 |
74 | return (
75 | {children}
76 | );
77 | }
78 |
--------------------------------------------------------------------------------
/src/providers/reducer.ts:
--------------------------------------------------------------------------------
1 | import { type GXSignalType } from '../contexts/types.js'
2 | import { type GXAction } from './types.js'
3 |
4 | const gxReducer = (
5 | signals: GXSignalType[],
6 | action: GXAction
7 | ): GXSignalType[] => {
8 | const signalName = action.type.split('/')[0]
9 |
10 | // Loop through all signals, make updates on targeted states
11 | // and returns a new array of signals (immutability).
12 | return signals.map(
13 | ({ name, operations, actions, asyncActions, state: prevState }) => {
14 | let state = prevState
15 |
16 | // Capture the target signal (a state and a bunch of actions) from the array of signals.
17 | // Capture the action from array of actions (of the target signal).
18 | // Run the action and update the signal state.
19 | if (name === signalName) {
20 | if (!action.isAsync) {
21 | for (const { type, handler } of actions) {
22 | if (type === action.type) {
23 | state = handler(prevState, action.payload)
24 | break
25 | }
26 | }
27 | } else {
28 | state = action.payload
29 | }
30 | }
31 |
32 | return {
33 | name,
34 | operations,
35 | state,
36 | actions,
37 | asyncActions
38 | }
39 | }
40 | )
41 | }
42 |
43 | export default gxReducer
44 |
--------------------------------------------------------------------------------
/src/providers/types.ts:
--------------------------------------------------------------------------------
1 | import { type AsyncActionStatusesType, type CreateStoreType } from '../helpers/types.js'
2 |
3 | /**
4 | * Props of the GX Provider
5 | */
6 | export interface GXProviderProps {
7 | // Children component of the GX Provider
8 | children: React.ReactNode
9 |
10 | // Collection of signals
11 | store: CreateStoreType
12 | }
13 |
14 | /**
15 | * Type of the actions
16 | */
17 | export interface GXAction {
18 | // Type of the action
19 | type: string
20 |
21 | // Nature of the action
22 | isAsync: boolean
23 |
24 | status?: AsyncActionStatusesType
25 |
26 | // Payload of the action
27 | payload?: any
28 | }
29 |
--------------------------------------------------------------------------------
/src/tests/gx/signals/counter.ts:
--------------------------------------------------------------------------------
1 | import createSignal from "../../../helpers/createSignal.js";
2 |
3 | const counterSignal = createSignal({
4 | name: "counter",
5 | state: 0,
6 | actions: {
7 | increment: (state, payload) => {
8 | return state + payload;
9 | },
10 |
11 | decrement: (state, payload) => {
12 | return state + payload;
13 | },
14 | },
15 | });
16 |
17 | export default counterSignal;
18 |
--------------------------------------------------------------------------------
/src/tests/gx/store/index.ts:
--------------------------------------------------------------------------------
1 | import createStore from "../../../helpers/createStore.js";
2 | import counterSignal from "../signals/counter.js";
3 |
4 | // Create a store
5 | const store = createStore([counterSignal]);
6 |
7 | export default store;
8 |
--------------------------------------------------------------------------------
/src/tests/signals.test.ts:
--------------------------------------------------------------------------------
1 | import counterSignal from "./gx/signals/counter.js";
2 |
3 | test("should create a signal", () => {
4 | // Expectations
5 | expect(counterSignal).not.toBeNull();
6 | expect(counterSignal.name).toEqual("counter");
7 | expect(counterSignal.state).toEqual(0);
8 | expect(counterSignal.actions.length).toEqual(2);
9 | expect(counterSignal.actions[0].handler(counterSignal.state, 3)).toEqual(3);
10 | });
11 |
--------------------------------------------------------------------------------
/src/tests/store.test.ts:
--------------------------------------------------------------------------------
1 | import counterSignal from './gx/signals/counter.js';
2 | import store from './gx/store/index.js';
3 |
4 | test("should create a store containing signals", () => {
5 | // Expectations
6 | expect(store.getSignals()).not.toBeNull();
7 | expect(store.getSignals()).toEqual([counterSignal]);
8 | });
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "rootDir": "src",
5 | "target": "ESNext",
6 | "lib": [
7 | "ES2015",
8 | "ESNext",
9 | "dom",
10 | "dom.iterable",
11 | "esnext"
12 | ],
13 | "allowJs": true,
14 | "skipLibCheck": true,
15 | "esModuleInterop": true,
16 | "allowSyntheticDefaultImports": true,
17 | "strict": false,
18 | "forceConsistentCasingInFileNames": true,
19 | "noFallthroughCasesInSwitch": true,
20 | "module": "ESNext",
21 | "moduleResolution": "node",
22 | "resolveJsonModule": true,
23 | "isolatedModules": true,
24 | "noEmit": false,
25 | "jsx": "react-jsx",
26 | "declaration": true,
27 | "sourceMap": false,
28 | },
29 | "include": [
30 | "src"
31 | ],
32 | "exclude": [
33 | "node_modules",
34 | "dist",
35 | "src/tests"
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------