Skip to content

Commit

Permalink
feat: add codebase and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
x8lucas8x committed Aug 1, 2019
1 parent f910910 commit b60256a
Show file tree
Hide file tree
Showing 20 changed files with 19,838 additions and 0 deletions.
21 changes: 21 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"parser": "@typescript-eslint/parser",
"extends": ["plugin:@typescript-eslint/recommended"],
"settings": {
"import/resolver": "webpack"
},
"env": {
"browser": true,
"node": true,
"jest/globals": true
},
"plugins": ["react", "import", "jest", "@typescript-eslint"],
"rules": {
"max-len": ["error", { "code": 120 }],
"react/jsx-filename-extension": "off",
"react/forbid-prop-types": "off",
"@typescript-eslint/indent": ["error", 2],
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/explicit-function-return-type": "off"
}
}
104 changes: 104 additions & 0 deletions __tests__/complexModel.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import {Model} from '../src';

const payload = {
'id': '123',
'author': {
'id': '1',
'name': 'Paul'
},
'title': 'My awesome blog post',
'comments': [
{
'id': '324',
'commenter': {
'id': '2',
'name': 'Nicole'
}
}
]
};

describe('Model with nested models', () => {
let userModel;
let commentModel;
let articleModel;

beforeAll(() => {
userModel = new Model({
namespace: 'users',
scopes: [],
fields: {},
});

commentModel = new Model({
namespace: 'comments',
scopes: [],
fields: {
commenter: userModel
},
});

articleModel = new Model({
namespace: 'articles',
scopes: ['byPage'],
fields: {
author: userModel,
comments: [commentModel]
},
});
});

describe('selectors', () => {
let reducedData;

beforeAll(() => {
const action = articleModel.actions().set('byPage', 1, [payload]);
reducedData = articleModel.reducers()({}, action);
});

it('returns expected article for defaultScope.providedId when using the articleModel', () => {
const byPageArticleSelector = articleModel.selectors(articleModel.defaultScope, payload.id);
expect(byPageArticleSelector(reducedData)).toEqual(payload);
});

it('returns expected comment for defaultScope.providedId when using the commentModel', () => {
const byPageCommentSelector = commentModel.selectors(commentModel.defaultScope, payload.comments[0].id);
expect(byPageCommentSelector(reducedData)).toEqual(payload.comments[0]);
});

it('returns expected user for defaultScope.providedId when using the userModel', () => {
const byPageUserSelector = userModel.selectors(userModel.defaultScope, payload.comments[0].commenter.id);
expect(byPageUserSelector(reducedData)).toEqual(payload.comments[0].commenter);
});
});

describe('reducers', () => {
it('set action sets the normalized nested data in the respective scopes', () => {
const action = articleModel.actions().set(articleModel.defaultScope, payload.id, [payload]);
const reducedData = articleModel.reducers()({}, action);
expect(reducedData).toEqual({
[articleModel.namespace]: {
[articleModel.defaultScope]: {
123: {
id: '123',
author: '1',
title: 'My awesome blog post',
comments: ['324']
}
}
},
[userModel.namespace]: {
[userModel.defaultScope]: {
1: {'id': '1', 'name': 'Paul'},
2: {'id': '2', 'name': 'Nicole'}
}
},
[commentModel.namespace]: {
[commentModel.defaultScope]: {
324: {id: '324', 'commenter': '2'}
}
}
});
});
});
});
84 changes: 84 additions & 0 deletions __tests__/data.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import {Data, Model} from '../src';

const page1Payload = {
'id': '123',
'title': 'My awesome blog post',
};

const page2Payload = {
'id': '321',
'title': 'My other blog post',
};

const customScope = 'byPage';

describe('Data', () => {
let articleModel;
let actions;
let reducers;
let dispatch;
let data;
let scopeId;

beforeAll(() => {
articleModel = new Model({
namespace: 'articles',
scopes: [customScope],
fields: {},
views: {
asOption: article => ({
value: article.id, label: `${article.id}. ${article.title}`, disabled: article.disabled || false,
}),
},
controllers: {
disable: article => { article.disabled = true },
},
});
actions = articleModel.actions();
reducers = articleModel.reducers();
});

beforeEach(() => {
dispatch = jest.fn();
scopeId = page1Payload.id;
const action = actions.set(customScope, scopeId, [page1Payload, page2Payload]);
const reducedData = reducers({}, action);
const byPageArticleSelector = articleModel.selectors(articleModel.defaultScope, page1Payload.id);
data = new Data(dispatch, articleModel, byPageArticleSelector(reducedData), customScope, scopeId);
});

describe('data is accessible via proxy', () => {
it('with id value', () => {
expect(data.id).toEqual(page1Payload.id);
});

it('with title value', () => {
expect(data.title).toEqual(page1Payload.title);
});
});

describe('views', () => {
it('asOption returns value, label and disabled defaulting as false', () => {
expect(data.asOption()).toEqual({
disabled: false,
label: "123. My awesome blog post",
value: "123"
});
});
});

describe('controllers', () => {
it('disable triggered a set action with a disabled flag', () => {
data.disable();
expect(dispatch).toHaveBeenCalledWith({
payload: [{
disabled: true,
...page1Payload,
}],
scope: customScope,
scopeId,
type: articleModel.actionTypes().set
});
});
});
});
41 changes: 41 additions & 0 deletions __tests__/hooks/useModel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as React from 'react';
import {combineModelReducers, Model, useModel} from '../../src';
import {createStore} from 'redux';
import {Provider} from 'react-redux';
import {mount} from 'enzyme';

function TestComponent({model}) {
const articleModel = useModel(model);
const article = articleModel.byId(1);

return (
<div>
{article.id}. {article.title}
</div>
);
}

describe('useModel', () => {
let articleModel;
let component;
let store;

beforeEach(() => {
articleModel = new Model({
namespace: 'articles',
scopes: [],
fields: {},
});
store = createStore(combineModelReducers([articleModel]));
store.dispatch(articleModel.actions().set(articleModel.defaultScope, 1, [{id: 1, title: 'title'}]));
component = mount(
<Provider store={store}>
<TestComponent model={articleModel} />
</Provider>
);
});

it('allows components to access a model state via its scopes', () => {
expect(component.find('div').text()).toEqual('1. title');
});
});
28 changes: 28 additions & 0 deletions __tests__/redux.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {Model, combineModelReducers} from '../src';

describe('combineModelReducers', () => {
let articleModel;
let reducersSpy;
let combinedModelReducers;

beforeEach(() => {
articleModel = new Model({
namespace: 'articles',
scopes: [],
fields: {},
});
reducersSpy = jest.spyOn(articleModel, 'reducers').mockImplementation(
// Implements an identity reducer
() => (data) => data
);
combinedModelReducers = combineModelReducers([articleModel]);
});

it('calls reducers in article model', () => {
expect(reducersSpy).toHaveBeenCalled();
});

it('reduces all models with the same state', () => {
expect(combinedModelReducers('what')).toEqual('what');
});
});
Loading

0 comments on commit b60256a

Please sign in to comment.