Skip to content
This repository has been archived by the owner on Nov 22, 2023. It is now read-only.

Commit

Permalink
migrate to new context api
Browse files Browse the repository at this point in the history
  • Loading branch information
Niryo committed May 18, 2019
1 parent 2f4c8a9 commit 631a117
Show file tree
Hide file tree
Showing 13 changed files with 154 additions and 148 deletions.
8 changes: 0 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,14 +225,6 @@ class DogController extends Controller{
Returns a state tree object with the current controller as the root of the tree. The state tree Object is an observable and it is always up to date, just like the controller's state.

* #### `setStateTree(stateTree: Object)`:

Sets the state tree to the given state tree object. If you are using `setStateTree`, you have to make sure that all the controlled components in your app receive a unique `serialID` via props.

* #### `addOnStateTreeChangeListener(listener: function)`:

Adds a listener for changes in the state tree. On every change to the state tree, the listener will be called with a string json object representing the current state tree, with the current controller as the root.

#### Controller Usage example:

```javascript
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "controllerim",
"version": "1.64.6",
"version": "2.0.0",
"description": "A simple, clean and well structured state management library for react",
"main": "dist/index.js",
"scripts": {
Expand Down
6 changes: 4 additions & 2 deletions src/AutoIndexManager/AutoIndexManager.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
export let shouldUseExperimentalAutoIndexing = false;
export const useExperimentalSerialization = (value) => shouldUseExperimentalAutoIndexing = value ===undefined ? true: value;
// export const useExperimentalSerialization = (value) => shouldUseExperimentalAutoIndexing = value ===undefined ? true: value;
export const useExperimentalSerialization = () => console.warn('experimental indexing currently not supported');

export class AutoIndexManager {
constructor(componentInstance, onIndexChangeCallback) {
this.component = componentInstance;
this.totalChildrenCount = 0;
this.currentFreeChildIndex = 0;
this.onIndexChangeCallback = onIndexChangeCallback;
this.parentIndexManager = this.component.context && this.component.context.autoIndexManager;
this.parentIndexManager = this.component.props.controllerimContext && this.component.props.controllerimContext.autoIndexManager;
if (this.parentIndexManager) {
this.increasTotalCountOnParent();
swizzleShouldComponentUpdate(this);
Expand Down Expand Up @@ -35,6 +36,7 @@ export class AutoIndexManager {
}

shouldUpdateIndex() {
console.log(this.parentIndexManager.totalChildrenCount)
return this.parentIndexManager.currentFreeChildIndex < this.parentIndexManager.totalChildrenCount;
}

Expand Down
2 changes: 1 addition & 1 deletion src/AutoIndexManager/AutoIndexManager.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ GrandChild.contextTypes = {
};


describe('IndexChildrenManager', () => {
describe.skip('IndexChildrenManager', () => {
beforeEach(() => {
indexesArray = [];
grandChildIndexArray = [];
Expand Down
28 changes: 13 additions & 15 deletions src/Controller/Controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class Controller {

addStateTreeFunctionality(this, privateScope);
exposeControllerNodeOnComponent(this, privateScope);
addGetChildContext(privateScope);
pushSelfToControllersArray(privateScope);
exposeStateOnScope(this, privateScope);
exposeGetParentControllerOnScope(this, privateScope);
exposeMockStateOnScope(this, privateScope);
Expand All @@ -54,17 +54,11 @@ export class Controller {
swizzleComponentWillUnmount(this, privateScope);
}
}
const addGetChildContext = (privateScope) => {
const pushSelfToControllersArray = (privateScope) => {
const componentInstance = privateScope.component;
componentInstance.getChildContext = function () {
let controllers = [];
if (componentInstance.context.controllers) {
controllers = [...this.context.controllers];
}
const controllerNode = componentInstance[CONTROLLER_NODE_PROP];
controllers.push(controllerNode);
return { controllers, stateTree: privateScope.stateTree.children, autoIndexManager: privateScope.autoIndexManager };
};
const controllerNode = componentInstance[CONTROLLER_NODE_PROP]; //check if we really need this
componentInstance.context.controllers.push(controllerNode);
componentInstance.props.controllerimContext.autoIndexManager = privateScope.autoIndexManager;
};
// const stateGuard = (internalState) => {
// if (isStateLocked(internalState) && internalState.initialState !== undefined) {
Expand Down Expand Up @@ -202,7 +196,7 @@ const exposeGetParentControllerOnScope = (publicScope, privateScope) => {
};

const staticGetParentController = (currentControllerName, component, parentControllerName) => {
let parentController = component.context.controllers && getControllerFromContext(component.context, parentControllerName);
let parentController = component.props.controllerimContext.controllers && getControllerFromContext(component.props.controllerimContext, parentControllerName);
if (!parentController && isTestMod()) {
parentController = getMockedParent(currentControllerName);
}
Expand Down Expand Up @@ -271,9 +265,10 @@ const markGetterOnPrivateScope = (privateScope) => {
const swizzleComponentWillUnmount = (publicScope, privateScope) => {
let originalMethod = privateScope.component.componentWillUnmount ? privateScope.component.componentWillUnmount.bind(privateScope.component) : () => { };
privateScope.component.componentWillUnmount = () => {
if(privateScope.component.context.stateTree){
const indexToRemove = privateScope.component.context.stateTree.findIndex(child => child === privateScope.stateTree);
privateScope.component.context.stateTree.splice(indexToRemove, 1);
const parentStateTreeChildren = privateScope.component.props.controllerimContext.parentStateTreeChildren;
if(parentStateTreeChildren){
const indexToRemove = parentStateTreeChildren.findIndex(child => child === privateScope.stateTree);
parentStateTreeChildren.splice(indexToRemove, 1);
}
originalMethod();
};
Expand All @@ -294,3 +289,6 @@ const extractControllerName = (instance) => {
return instance.constructor.name;
}
};


//todo: swizzle component will mount todo all the context related magic.
143 changes: 71 additions & 72 deletions src/Controller/Controller.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,19 @@ import { TestUtils } from '../TestUtils/testUtils';
import {
Parent, ComponentThatForgetToPassThis,
ComponentThatAskForNonExistentParent,
ComponentThatPutOneStateInsideAnother,
ComponentThatFetchSiblingController,
ComponentThatInitControllerInConstructor,
ParentThatCanHideChild,
BasicStateTree,
ComponentWithSeralizableChild,
BasicChild,
ComponentThatOnlyRenderItsChildren,
// BasicChild,
// ComponentThatOnlyRenderItsChildren,
// SerializableTree,
// ComponentWithMissingSerialID,
// ComplexStateTree
} from './TestComponents';

const getFakeComponentInstacne = (controllers) => {
const getFakeComponentInstance = (controllers) => {
controllers = controllers || [];
controllers.forEach(controller => controller.stateTree = {
listenersLinkedList: {
Expand All @@ -34,7 +33,7 @@ const getFakeComponentInstacne = (controllers) => {
}
}
}, );
return { context: { controllers } };
return { context: { controllers }, props: {controllerimContext: { controllers: [...controllers] }, controllerimStateTreeNode: {state: {}, children: []}, testModeID: Math.random()} };
};


Expand All @@ -52,15 +51,15 @@ describe('Controller', () => {

it('should memoize getting parent controller', () => {
const controllers = [{ name: 'someParent', instance: 'mocekdParentController' }];
const testController = new Controller(getFakeComponentInstacne(controllers));
const testController = new Controller(getFakeComponentInstance(controllers));
expect(testController.getParentController('someParent')).toEqual('mocekdParentController');
controllers[0].instance = 'changedMocked';
//change will not take effect because of memoization:
expect(testController.getParentController('someParent')).toEqual('mocekdParentController');
});

xit('should throw an error when trying to set the state from outside of the contoller', () => {
const testController = new Controller(getFakeComponentInstacne());
const testController = new Controller(getFakeComponentInstance());
testController.state = { FirstChangeAllwaysAllowed: 'change' };
//after state is set for the first time, no changes outside the controller are allowed:
expect(() => testController.state = { bla: 'bla' }).toThrowError('Cannot set state from outside of a controller');
Expand All @@ -71,13 +70,13 @@ describe('Controller', () => {
class ControllerWithoutGetName extends Controller{}
const originalConsole = console.warn;
console.warn = jest.fn();
new ControllerWithoutGetName(getFakeComponentInstacne());
new ControllerWithoutGetName(getFakeComponentInstance());
expect(console.warn).toHaveBeenCalledWith(`Warning: controllers must have a static member "controllerName". Please add it to ControllerWithoutGetName`);
console.warn.mockReset();
expect(console.warn).not.toHaveBeenCalled();
class ControllerWithName extends Controller{}
ControllerWithName.controllerName = 'I am a name!';
new ControllerWithoutGetName(getFakeComponentInstacne());
new ControllerWithoutGetName(getFakeComponentInstance());
console.warn = originalConsole;
});

Expand Down Expand Up @@ -151,13 +150,13 @@ describe('Controller', () => {
});

it(`should throw an error if trying to save into state other controller's state`, () => {
const component = mount(<ComponentThatPutOneStateInsideAnother />);
const component = mount(<Parent />);
global.Proxy = backupProxy;
expect(() => component.find('[data-hook="mixStates"]').simulate('click')).toThrowError(`Cannot set state with other controller's state.`);
});

it(`should throw an error if trying to save into state other controller's part of state`, () => {
const component = mount(<ComponentThatPutOneStateInsideAnother />);
const component = mount(<Parent />);
global.Proxy = backupProxy;
expect(() => component.find('[data-hook="mixPartOfState"]').simulate('click')).toThrowError(`Cannot set state with other controller's state.`);
});
Expand Down Expand Up @@ -608,68 +607,68 @@ describe('Controller', () => {
expect(didUpdate).toHaveBeenCalled();
});

it('should add children indexes as serialID when activating useExperimentalIndexing()', () => {
useExperimentalSerialization();
const component = mount(<BasicStateTree />);
const controller = TestUtils.getControllerOf(component.instance());
const expectedValue = {
'name': 'Acon',
'serialID': 'controllerim_root',
'state': {
'a': 'a'
},
'children': [
{
'serialID': 0,
'name': 'Bcon',
'state': {
'b': 'b'
},
'children': [
{
'serialID': 0,
'name': 'Ccon',
'state': {
'c': 'c'
},
'children': [

]
}
]
},
{
'serialID': 1,
'name': 'Ccon',
'state': {
'c': 'c'
},
'children': [

]
}
]
};
expect(controller.getStateTree()).toEqual(expectedValue);
});
// it('should add children indexes as serialID when activating useExperimentalIndexing()', () => {
// useExperimentalSerialization();
// const component = mount(<BasicStateTree />);
// const controller = TestUtils.getControllerOf(component.instance());
// const expectedValue = {
// 'name': 'Acon',
// 'serialID': '0',
// 'state': {
// 'a': 'a'
// },
// 'children': [
// {
// 'serialID': 0,
// 'name': 'Bcon',
// 'state': {
// 'b': 'b'
// },
// 'children': [
// {
// 'serialID': 0,
// 'name': 'Ccon',
// 'state': {
// 'c': 'c'
// },
// 'children': [

// ]
// }
// ]
// },
// {
// 'serialID': 1,
// 'name': 'Ccon',
// 'state': {
// 'c': 'c'
// },
// 'children': [

// ]
// }
// ]
// };
// expect(controller.getStateTree()).toEqual(expectedValue);
// });

it('should handle ownerless children correctly when auto indexing', () => {
useExperimentalSerialization();
const expectedValue = {
'name': 'ComponentWithSeralizableChildController',
'serialID': 'controllerim_root',
'state': {},
'children': [{
'name': 'BasicChildController',
'serialID': 0,
'state': {},
'children': []
}]
};
const component = mount(<ComponentThatOnlyRenderItsChildren><BasicChild /></ComponentThatOnlyRenderItsChildren>);
const controller = TestUtils.getControllerOf(component.instance());
expect(controller.getStateTree()).toEqual(expectedValue);
});
// it('should handle ownerless children correctly when auto indexing', () => {
// useExperimentalSerialization();
// const expectedValue = {
// 'name': 'ComponentWithSeralizableChildController',
// 'serialID': 'controllerim_root',
// 'state': {},
// 'children': [{
// 'name': 'BasicChildController',
// 'serialID': 0,
// 'state': {},
// 'children': []
// }]
// };
// const component = mount(<ComponentThatOnlyRenderItsChildren><BasicChild /></ComponentThatOnlyRenderItsChildren>);
// const controller = TestUtils.getControllerOf(component.instance());
// expect(controller.getStateTree()).toEqual(expectedValue);
// });
});
}
});
Expand Down
19 changes: 7 additions & 12 deletions src/Controller/StateTree.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
import { shouldUseExperimentalAutoIndexing, AutoIndexManager } from '../AutoIndexManager/AutoIndexManager';
import { immutableProxy } from '../ImmutableProxy/immutableProxy';


const ROOT_CONTROLLER_SERIAL_ID = 'controllerim_root';

export const addStateTreeFunctionality = (publicScope, privateScope) => {
initStateTree(publicScope, privateScope);
exposeGetStateTreeOnScope(publicScope, privateScope);
Expand All @@ -14,21 +12,18 @@ export const addStateTreeFunctionality = (publicScope, privateScope) => {

const initStateTree = (publicScope, privateScope) => {
const serialID = privateScope.component.props && privateScope.component.props.serialID;
let newstateTreeNode = {
name: privateScope.controllerName,
state: {},
children: []
};
let stateTreeNode = privateScope.component.props.controllerimStateTreeNode;
stateTreeNode.name = privateScope.controllerName;

// newstateTreeNode = shallowProxify(newstateTreeNode);
if (serialID !== undefined) {
newstateTreeNode.serialID = serialID;
stateTreeNode.serialID = serialID;
}
privateScope.stateTree = newstateTreeNode;
if (privateScope.component.context.stateTree) {
privateScope.component.context.stateTree.push(newstateTreeNode);
privateScope.stateTree = stateTreeNode;
if (privateScope.component.props.controllerimContext.parentStateTreeChildren) {
privateScope.component.props.controllerimContext.parentStateTreeChildren.push(stateTreeNode);
} else {
newstateTreeNode.serialID = ROOT_CONTROLLER_SERIAL_ID;
stateTreeNode.serialID = ROOT_CONTROLLER_SERIAL_ID;
}

if (shouldUseExperimentalAutoIndexing) {
Expand Down
5 changes: 5 additions & 0 deletions src/Controller/TestComponents/Child.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export class ChildController extends Controller {
getState(){
return this.state;
}
setAnotherState(state) {
this.state = state;
}
setPartialState(someObj) {
this.state.someObj = someObj;
}
Expand All @@ -39,6 +42,8 @@ export const Child = observer(class extends React.Component {
<div data-hook="propFromParentFromWithingController">{this.controller.getPropFromParent()}</div>
<div data-hook="someChildProp">{this.controller.getChildProp()}</div>
<button data-hook="changeChildProp" onClick={() => this.controller.changeChildProp()} />
<button data-hook="mixStates" onClick={() => this.controller.setAnotherState(this.parentController.getState())} />
<button data-hook="mixPartOfState" onClick={() => this.controller.setPartialState(this.parentController.getObjectProp())} />
</div>
);
}
Expand Down
Loading

0 comments on commit 631a117

Please sign in to comment.