<commit_before>
import {expectSaga} from 'redux-saga-test-plan'
import * as matchers from 'redux-saga-test-plan/matchers'
import {call} from 'redux-saga/effects'
import {cache} from 'tocco-util'

import {requestSaga} from './rest'
import * as helpers from './helpers'

describe('app-extensions', () => {
  describe('rest', () => {
    describe('helpers', () => {
      beforeEach(() => {
        cache.clearAll()
      })

      describe('buildRequestQuery', () => {
        test('should return an empty object with no options', () => {
          expect(helpers.buildRequestQuery()).to.eql({})
        })

        test('should return object with valid params', () => {
          const options = {
            paths: ['f1', 'f2'],
            conditions: {firstname: 'test'},
            filter: ['filter1', 'filter2'],
            random: 1
          }

          const params = helpers.buildRequestQuery(options)

          const expectedParams = {
            paths: ['f1', 'f2'],
            conditions: {firstname: 'test'},
            filter: ['filter1', 'filter2']
          }

          expect(params).to.eql(expectedParams)
        })

        test('should set exclamation mark for fields and relations if explicitly empty', () => {
          const options = {
            fields: [],
            relations: []
          }

          const params = helpers.buildRequestQuery(options)

          expect(params).to.eql({
            fields: '!',
            relations: '!'
          })
        })

        test('should set sort if sorting is valid', () => {
          const options = {
            sorting: [{field: 'firstname', order: 'desc'}]
          }

          const params = helpers.buildRequestQuery(options)

          expect(params).to.eql({
            sort: 'firstname desc'
          })
        })

        test('should not sort if oderBy is not valid', () => {
          const options = {
            orderBy: [{id: 'firstname'}]
          }

          const params = helpers.buildRequestQuery(options)
          expect(params).to.eql({})
        })

        test('should set limit and offset accordingly', () => {
          const options = {
            limit: 20,
            page: 3
          }

          const params = helpers.buildRequestQuery(options)
          expect(params).to.eql({
            limit: 20,
            offset: 40
          })
        })
      })

      describe('requestQueryToUrlParams', () => {
        test('should prepend lodash to attribute name and join arrays', () => {
          const input = {
            limit: 20,
            offset: 40,
            paths: ['firstname', 'lastname'],
            search: 'asd'
          }

          const expectedResult = {
            _limit: 20,
            _offset: 40,
            _paths: 'firstname,lastname',
            _search: 'asd'

          }
          expect(helpers.requestQueryToUrlParams(input)).to.eql(expectedResult)
        })

        test('should attach conditions to object and let them be arrays', () => {
          const input = {
            limit: 10,
            conditions: {
              firstname: ['dake', 'rott']
            }
          }

          const expectedResult = {
            _limit: 10,
            firstname: ['dake', 'rott']
          }
          expect(helpers.requestQueryToUrlParams(input)).to.eql(expectedResult)
        })
      })

      describe('fetchEntity', () => {
        test('should call fetch', async() => {
          const query = {
            paths: ['firstname', 'lastname'],
            forms: 'User_detail'
          }
          const responseEntity = {paths: {firstname: 'Jack'}}

          await expectSaga(helpers.fetchEntity, 'User', '1', query)
            .provide([
              [matchers.call.fn(requestSaga), {body: responseEntity}]
            ])
            .returns(responseEntity)
            .run()
        })
      })

      describe('fetchEntities', () => {
        test('should call fetch', () => {
          const query = {
            paths: ['firstname', 'lastname'],
            forms: 'User_detail'
          }

          const responseEntities = [{}, {}]

          return expectSaga(helpers.fetchEntities, 'User', query)
            .provide([
              [matchers.call.fn(requestSaga), {body: {data: responseEntities}}]
            ])
            .returns(responseEntities)
            .run()
        })
      })

      describe('fetchDisplay', () => {
        test('should call fetch', () => {
          const response = {
            data: [
              {
                model: 'User',
                values: [
                  {
                    key: '1',
                    display: 'Test'
                  }
                ]
              }
            ]
          }

          return expectSaga(helpers.fetchDisplay, 'User', '1')
            .provide([
              [matchers.call.fn(requestSaga), {body: response}]
            ])
            .returns('Test')
            .run()
        })

        test('should serve display from cache if already loaded', () => {
          cache.addShortTerm('display', 'User.1', 'Test')

          return expectSaga(helpers.fetchDisplay, 'User', '1')
            .returns('Test')
            .run()
        })

        test('should serve display with type from cache if already loaded', () => {
          cache.addShortTerm('display', 'User.1.tooltip', 'Test')

          return expectSaga(helpers.fetchDisplay, 'User', '1', 'tooltip')
            .returns('Test')
            .run()
        })
      })

      describe('fetchDisplayExpressions', () => {
        test('should call requestSaga and transform response', () => {
          const formName = 'User'
          const scope = 'list'
          const entityKeys = ['1', '33']
          const fields = ['display1', 'display2']

          const responseFake = {
            formName,
            displayExpressions: [
              {
                key: '1',
                displayExpressions: {
                  display1: '1-1',
                  display2: '1-2'
                }
              },
              {
                key: '33',
                displayExpressions: {
                  display1: '33-1',
                  display2: '33-2'
                }
              }
            ]
          }

          const expectedResult = {
            1: {
              display1: '1-1',
              display2: '1-2'
            },
            33: {
              display1: '33-1',
              display2: '33-2'
            }
          }

          const entityName = 'User'

          return expectSaga(helpers.fetchDisplayExpressions, formName, scope, entityKeys, fields, entityName)
            .provide([
              [matchers.call.fn(requestSaga), {body: responseFake}]
            ])
            .returns(expectedResult)
            .run()
        })
      })

      describe('fetchDisplays', () => {
        test('should transform input and response to objects', () => {
          const request = {
            User_status: ['2293'],
            Gender: ['1', '2']
          }

          const expectedOptions = {
            method: 'POST',
            body: {
              data: [{
                model: 'User_status',
                keys: ['2293']
              },
              {
                model: 'Gender',
                keys:
                    ['1', '2']
              }
              ]
            }
          }

          const responseFake = {
            body: {
              data: [{
                model: 'User_status',
                values: [{
                  key: '2293',
                  display: 'Hans Muster 123'
                }]
              },
              {
                model: 'Gender',
                values: [{
                  key: '1',
                  display: 'Male'
                }, {
                  key: '2',
                  display: 'Female'
                }]
              }]
            }
          }

          const expectedResult = {
            User_status: {
              2293: 'Hans Muster 123'
            },
            Gender: {
              1: 'Male',
              2: 'Female'
            }
          }

          return expectSaga(helpers.fetchDisplays, request)
            .provide([
              [matchers.call.fn(requestSaga), responseFake]
            ])
            .call(requestSaga, 'entities/2.0/displays', expectedOptions)
            .returns(expectedResult)
            .run()
        })

        test('should only load uncached displays and combine results', () => {
          cache.addShortTerm('display', 'Gender.1', 'Male')

          const request = {
            Gender: ['1', '2']
          }

          const expectedOptions = {
            method: 'POST',
            body: {
              data: [
                {
                  model: 'Gender',
                  keys:
                    ['2']
                }
              ]
            }
          }

          const responseFake = {
            body: {
              data: [
                {
                  model: 'Gender',
                  values: [
                    {
                      key: '2',
                      display: 'Female'
                    }
                  ]
                }
              ]
            }
          }

          const expectedResult = {
            Gender: {
              1: 'Male',
              2: 'Female'
            }
          }

          return expectSaga(helpers.fetchDisplays, request)
            .provide([
              [matchers.call.fn(requestSaga), responseFake]
            ])
            .call(requestSaga, 'entities/2.0/displays', expectedOptions)
            .returns(expectedResult)
            .run()
        })

        test('should not request anything if all displays are cached', () => {
          cache.addShortTerm('display', 'Gender.1', 'Male')
          cache.addShortTerm('display', 'Gender.2', 'Female')

          const request = {
            Gender: ['1', '2']
          }

          const expectedResult = {
            Gender: {
              1: 'Male',
              2: 'Female'
            }
          }

          return expectSaga(helpers.fetchDisplays, request)
            .returns(expectedResult)
            .run()
        })
      })

      describe('fetchEntityCount', () => {
        test('should call fetch', () => {
          const query = {
            paths: ['firstname', 'lastname'],
            forms: 'User_detail'
          }

          const responseCount = {count: 1}

          return expectSaga(helpers.fetchEntityCount, 'User', query)
            .provide([
              [matchers.call.fn(requestSaga), {body: responseCount}]
            ])
            .returns(responseCount.count)
            .run()
        })
      })

      describe('fetchForm', () => {
        const formName = 'User'
        const mode = 'udpate'
        const resp = {body: {form: {}}}
        const transformedResponse = {}

        test('should call fetch and use default transformer', () => {
          const gen = helpers.fetchForm(formName, mode)
          expect(gen.next().value).to.eql(call(requestSaga, `forms/${formName}/${mode}`, {}))

          expect(gen.next(resp).value).to.eql(call(helpers.defaultFormTransformer, resp.body))

          const next = gen.next(transformedResponse)
          expect(next.value).to.equal(transformedResponse)
          expect(next.done).to.be.true
        })

        test('should cache the form after first fetch', async() => {
          const formMock = {form: {}}

          const result = await expectSaga(helpers.fetchForm, 'User', 'update')
            .provide([
              [matchers.call.fn(requestSaga), {body: formMock}]
            ])
            .call.like({fn: requestSaga})
            .run()

          return expectSaga(helpers.fetchForm, 'User', 'update')
            .returns(result.returnValue)
            .not.call.like({fn: requestSaga})
            .run()
        })

        test('should return null on allowed 404', async() => {
          return expectSaga(helpers.fetchForm, 'User', 'search', true)
            .provide([
              [matchers.call.fn(requestSaga), {status: 404}]
            ])
            .returns(null)
            .run()
        })

        describe('defaulFormTransformer', () => {
          test('should return form propery', () => {
            const inputForm = {form: {children: []}}
            const expectedForm = {children: []}
            expect(helpers.defaultFormTransformer(inputForm)).to.eql(expectedForm)
          })
        })
      })

      describe('fetchSearchFilters', () => {
        const entity = 'User'
        const resp = {body: {filters: [{uniqueId: 'activeUser'}]}}

        test('should call request saga and return filters property ', () => {
          return expectSaga(helpers.fetchSearchFilters, entity)
            .provide([
              [matchers.call.fn(requestSaga), resp]
            ])
            .returns(resp.body.filters)
            .run()
        })
      })

      describe('fetchModel', () => {
        const entity = 'User'
        const resp = {body: {}}
        const transformedResponse = {}

        test('should call fetch and use default transformer', () => {
          const gen = helpers.fetchModel(entity)
          expect(gen.next().value).to.eql(call(requestSaga, `entities/${entity}/model`))

          expect(gen.next(resp).value).to.eql(call(helpers.defaultModelTransformer, resp.body))

          const next = gen.next(transformedResponse)
          expect(next.value).to.equal(transformedResponse)
          expect(next.done).to.be.true
        })

        test('should cache the model after first fetch', async() => {
          const mockModel = {
            name: 'User',
            label: 'Person',
            fields: [
              {
                fieldName: 'pk',
                type: 'serial',
                validation: {}
              }
            ],
            relations: []

          }

          const result = await expectSaga(helpers.fetchModel, 'User')
            .provide([
              [matchers.call.fn(requestSaga), {body: mockModel}]
            ])
            .call.like({fn: requestSaga})
            .run()

          return expectSaga(helpers.fetchModel, 'User')
            .returns(result.returnValue)
            .not.call.like({fn: requestSaga})
            .run()
        })

        describe('defaultModelTransformer', () => {
          test('should return flatten object ', () => {
            const inputModel = {
              label: 'Test',
              name: 'test',
              fields: [
                {
                  fieldName: 'pk',
                  type: 'serial',
                  validation: {}
                },
                {
                  fieldName: 'active',
                  type: 'boolean',
                  validation: {
                    mandatory: false
                  }
                }
              ],
              relations: [
                {
                  relationName: 'relUser',
                  targetEntity: 'User',
                  reverseRelationName: 'relDummySubGrid',
                  multi: false
                }
              ]

            }

            const expectedModel = {
              label: 'Test',
              name: 'test',
              paths: {
                active: {
                  fieldName: 'active',
                  type: 'boolean',
                  validation: {
                    mandatory: false
                  }
                },
                pk: {
                  fieldName: 'pk',
                  type: 'serial',
                  validation: {}
                },
                relUser: {
                  multi: false,
                  relationName: 'relUser',
                  reverseRelationName: 'relDummySubGrid',
                  targetEntity: 'User',
                  type: 'relation'
                }
              }
            }

            const result = helpers.defaultModelTransformer(inputModel)
            expect(result).to.eql(expectedModel)
          })
        })

        describe('flattenObjectValues', () => {
          test('should extract value attributes of object values', () => {
            const values = {
              'relUser.pk': {value: '1', display: 'Tocco'},
              'firstname': 'Test'
            }

            const expectedResult = {
              'relUser.pk': '1',
              'firstname': 'Test'
            }

            const result = helpers.flattenObjectValues(values)
            expect(result).to.eql(expectedResult)
          })
        })

        describe('fetchPrincipal', () => {
          test('should call requestSaga and return the principals info object', () => {
            const response = {
              username: 'tocco',
              businessUnit: 'test1'
            }

            const expectedReturn = {
              username: 'tocco',
              currentBusinessUnit: 'test1'
            }

            return expectSaga(helpers.fetchPrincipal)
              .provide([
                [matchers.call.fn(requestSaga), {body: response}]
              ])
              .returns(expectedReturn)
              .run()
          })

          test('should load cached principal info object after fetch', async() => {
            const expectedReturn = {
              username: 'tocco',
              currentBusinessUnit: 'test1'
            }

            cache.addShortTerm('session', 'principal', expectedReturn)

            return expectSaga(helpers.fetchPrincipal)
              .returns(expectedReturn)
              .run()
          })
        })
      })
    })
  })
})
<commit_msg>test(app-extensions): add tests for createSortingString<commit_after>
import {expectSaga} from 'redux-saga-test-plan'
import * as matchers from 'redux-saga-test-plan/matchers'
import {call} from 'redux-saga/effects'
import {cache} from 'tocco-util'

import {requestSaga} from './rest'
import * as helpers from './helpers'

describe('app-extensions', () => {
  describe('rest', () => {
    describe('helpers', () => {
      beforeEach(() => {
        cache.clearAll()
      })

      describe('buildRequestQuery', () => {
        test('should return an empty object with no options', () => {
          expect(helpers.buildRequestQuery()).to.eql({})
        })

        test('should return object with valid params', () => {
          const options = {
            paths: ['f1', 'f2'],
            conditions: {firstname: 'test'},
            filter: ['filter1', 'filter2'],
            random: 1
          }

          const params = helpers.buildRequestQuery(options)

          const expectedParams = {
            paths: ['f1', 'f2'],
            conditions: {firstname: 'test'},
            filter: ['filter1', 'filter2']
          }

          expect(params).to.eql(expectedParams)
        })

        test('should set exclamation mark for fields and relations if explicitly empty', () => {
          const options = {
            fields: [],
            relations: []
          }

          const params = helpers.buildRequestQuery(options)

          expect(params).to.eql({
            fields: '!',
            relations: '!'
          })
        })

        test('should set sort if sorting is valid', () => {
          const options = {
            sorting: [{field: 'firstname', order: 'desc'}]
          }

          const params = helpers.buildRequestQuery(options)

          expect(params).to.eql({
            sort: 'firstname desc'
          })
        })

        test('should not sort if oderBy is not valid', () => {
          const options = {
            orderBy: [{id: 'firstname'}]
          }

          const params = helpers.buildRequestQuery(options)
          expect(params).to.eql({})
        })

        test('should set limit and offset accordingly', () => {
          const options = {
            limit: 20,
            page: 3
          }

          const params = helpers.buildRequestQuery(options)
          expect(params).to.eql({
            limit: 20,
            offset: 40
          })
        })
      })

      describe('requestQueryToUrlParams', () => {
        test('should prepend lodash to attribute name and join arrays', () => {
          const input = {
            limit: 20,
            offset: 40,
            paths: ['firstname', 'lastname'],
            search: 'asd'
          }

          const expectedResult = {
            _limit: 20,
            _offset: 40,
            _paths: 'firstname,lastname',
            _search: 'asd'

          }
          expect(helpers.requestQueryToUrlParams(input)).to.eql(expectedResult)
        })

        test('should attach conditions to object and let them be arrays', () => {
          const input = {
            limit: 10,
            conditions: {
              firstname: ['dake', 'rott']
            }
          }

          const expectedResult = {
            _limit: 10,
            firstname: ['dake', 'rott']
          }
          expect(helpers.requestQueryToUrlParams(input)).to.eql(expectedResult)
        })
      })

      describe('fetchEntity', () => {
        test('should call fetch', async() => {
          const query = {
            paths: ['firstname', 'lastname'],
            forms: 'User_detail'
          }
          const responseEntity = {paths: {firstname: 'Jack'}}

          await expectSaga(helpers.fetchEntity, 'User', '1', query)
            .provide([
              [matchers.call.fn(requestSaga), {body: responseEntity}]
            ])
            .returns(responseEntity)
            .run()
        })
      })

      describe('fetchEntities', () => {
        test('should call fetch', () => {
          const query = {
            paths: ['firstname', 'lastname'],
            forms: 'User_detail'
          }

          const responseEntities = [{}, {}]

          return expectSaga(helpers.fetchEntities, 'User', query)
            .provide([
              [matchers.call.fn(requestSaga), {body: {data: responseEntities}}]
            ])
            .returns(responseEntities)
            .run()
        })
      })

      describe('fetchDisplay', () => {
        test('should call fetch', () => {
          const response = {
            data: [
              {
                model: 'User',
                values: [
                  {
                    key: '1',
                    display: 'Test'
                  }
                ]
              }
            ]
          }

          return expectSaga(helpers.fetchDisplay, 'User', '1')
            .provide([
              [matchers.call.fn(requestSaga), {body: response}]
            ])
            .returns('Test')
            .run()
        })

        test('should serve display from cache if already loaded', () => {
          cache.addShortTerm('display', 'User.1', 'Test')

          return expectSaga(helpers.fetchDisplay, 'User', '1')
            .returns('Test')
            .run()
        })

        test('should serve display with type from cache if already loaded', () => {
          cache.addShortTerm('display', 'User.1.tooltip', 'Test')

          return expectSaga(helpers.fetchDisplay, 'User', '1', 'tooltip')
            .returns('Test')
            .run()
        })
      })

      describe('fetchDisplayExpressions', () => {
        test('should call requestSaga and transform response', () => {
          const formName = 'User'
          const scope = 'list'
          const entityKeys = ['1', '33']
          const fields = ['display1', 'display2']

          const responseFake = {
            formName,
            displayExpressions: [
              {
                key: '1',
                displayExpressions: {
                  display1: '1-1',
                  display2: '1-2'
                }
              },
              {
                key: '33',
                displayExpressions: {
                  display1: '33-1',
                  display2: '33-2'
                }
              }
            ]
          }

          const expectedResult = {
            1: {
              display1: '1-1',
              display2: '1-2'
            },
            33: {
              display1: '33-1',
              display2: '33-2'
            }
          }

          const entityName = 'User'

          return expectSaga(helpers.fetchDisplayExpressions, formName, scope, entityKeys, fields, entityName)
            .provide([
              [matchers.call.fn(requestSaga), {body: responseFake}]
            ])
            .returns(expectedResult)
            .run()
        })
      })

      describe('fetchDisplays', () => {
        test('should transform input and response to objects', () => {
          const request = {
            User_status: ['2293'],
            Gender: ['1', '2']
          }

          const expectedOptions = {
            method: 'POST',
            body: {
              data: [{
                model: 'User_status',
                keys: ['2293']
              },
              {
                model: 'Gender',
                keys:
                    ['1', '2']
              }
              ]
            }
          }

          const responseFake = {
            body: {
              data: [{
                model: 'User_status',
                values: [{
                  key: '2293',
                  display: 'Hans Muster 123'
                }]
              },
              {
                model: 'Gender',
                values: [{
                  key: '1',
                  display: 'Male'
                }, {
                  key: '2',
                  display: 'Female'
                }]
              }]
            }
          }

          const expectedResult = {
            User_status: {
              2293: 'Hans Muster 123'
            },
            Gender: {
              1: 'Male',
              2: 'Female'
            }
          }

          return expectSaga(helpers.fetchDisplays, request)
            .provide([
              [matchers.call.fn(requestSaga), responseFake]
            ])
            .call(requestSaga, 'entities/2.0/displays', expectedOptions)
            .returns(expectedResult)
            .run()
        })

        test('should only load uncached displays and combine results', () => {
          cache.addShortTerm('display', 'Gender.1', 'Male')

          const request = {
            Gender: ['1', '2']
          }

          const expectedOptions = {
            method: 'POST',
            body: {
              data: [
                {
                  model: 'Gender',
                  keys:
                    ['2']
                }
              ]
            }
          }

          const responseFake = {
            body: {
              data: [
                {
                  model: 'Gender',
                  values: [
                    {
                      key: '2',
                      display: 'Female'
                    }
                  ]
                }
              ]
            }
          }

          const expectedResult = {
            Gender: {
              1: 'Male',
              2: 'Female'
            }
          }

          return expectSaga(helpers.fetchDisplays, request)
            .provide([
              [matchers.call.fn(requestSaga), responseFake]
            ])
            .call(requestSaga, 'entities/2.0/displays', expectedOptions)
            .returns(expectedResult)
            .run()
        })

        test('should not request anything if all displays are cached', () => {
          cache.addShortTerm('display', 'Gender.1', 'Male')
          cache.addShortTerm('display', 'Gender.2', 'Female')

          const request = {
            Gender: ['1', '2']
          }

          const expectedResult = {
            Gender: {
              1: 'Male',
              2: 'Female'
            }
          }

          return expectSaga(helpers.fetchDisplays, request)
            .returns(expectedResult)
            .run()
        })
      })

      describe('fetchEntityCount', () => {
        test('should call fetch', () => {
          const query = {
            paths: ['firstname', 'lastname'],
            forms: 'User_detail'
          }

          const responseCount = {count: 1}

          return expectSaga(helpers.fetchEntityCount, 'User', query)
            .provide([
              [matchers.call.fn(requestSaga), {body: responseCount}]
            ])
            .returns(responseCount.count)
            .run()
        })
      })

      describe('fetchForm', () => {
        const formName = 'User'
        const mode = 'udpate'
        const resp = {body: {form: {}}}
        const transformedResponse = {}

        test('should call fetch and use default transformer', () => {
          const gen = helpers.fetchForm(formName, mode)
          expect(gen.next().value).to.eql(call(requestSaga, `forms/${formName}/${mode}`, {}))

          expect(gen.next(resp).value).to.eql(call(helpers.defaultFormTransformer, resp.body))

          const next = gen.next(transformedResponse)
          expect(next.value).to.equal(transformedResponse)
          expect(next.done).to.be.true
        })

        test('should cache the form after first fetch', async() => {
          const formMock = {form: {}}

          const result = await expectSaga(helpers.fetchForm, 'User', 'update')
            .provide([
              [matchers.call.fn(requestSaga), {body: formMock}]
            ])
            .call.like({fn: requestSaga})
            .run()

          return expectSaga(helpers.fetchForm, 'User', 'update')
            .returns(result.returnValue)
            .not.call.like({fn: requestSaga})
            .run()
        })

        test('should return null on allowed 404', async() => {
          return expectSaga(helpers.fetchForm, 'User', 'search', true)
            .provide([
              [matchers.call.fn(requestSaga), {status: 404}]
            ])
            .returns(null)
            .run()
        })

        describe('defaulFormTransformer', () => {
          test('should return form propery', () => {
            const inputForm = {form: {children: []}}
            const expectedForm = {children: []}
            expect(helpers.defaultFormTransformer(inputForm)).to.eql(expectedForm)
          })
        })
      })

      describe('fetchSearchFilters', () => {
        const entity = 'User'
        const resp = {body: {filters: [{uniqueId: 'activeUser'}]}}

        test('should call request saga and return filters property ', () => {
          return expectSaga(helpers.fetchSearchFilters, entity)
            .provide([
              [matchers.call.fn(requestSaga), resp]
            ])
            .returns(resp.body.filters)
            .run()
        })
      })

      describe('fetchModel', () => {
        const entity = 'User'
        const resp = {body: {}}
        const transformedResponse = {}

        test('should call fetch and use default transformer', () => {
          const gen = helpers.fetchModel(entity)
          expect(gen.next().value).to.eql(call(requestSaga, `entities/${entity}/model`))

          expect(gen.next(resp).value).to.eql(call(helpers.defaultModelTransformer, resp.body))

          const next = gen.next(transformedResponse)
          expect(next.value).to.equal(transformedResponse)
          expect(next.done).to.be.true
        })

        test('should cache the model after first fetch', async() => {
          const mockModel = {
            name: 'User',
            label: 'Person',
            fields: [
              {
                fieldName: 'pk',
                type: 'serial',
                validation: {}
              }
            ],
            relations: []

          }

          const result = await expectSaga(helpers.fetchModel, 'User')
            .provide([
              [matchers.call.fn(requestSaga), {body: mockModel}]
            ])
            .call.like({fn: requestSaga})
            .run()

          return expectSaga(helpers.fetchModel, 'User')
            .returns(result.returnValue)
            .not.call.like({fn: requestSaga})
            .run()
        })

        describe('defaultModelTransformer', () => {
          test('should return flatten object ', () => {
            const inputModel = {
              label: 'Test',
              name: 'test',
              fields: [
                {
                  fieldName: 'pk',
                  type: 'serial',
                  validation: {}
                },
                {
                  fieldName: 'active',
                  type: 'boolean',
                  validation: {
                    mandatory: false
                  }
                }
              ],
              relations: [
                {
                  relationName: 'relUser',
                  targetEntity: 'User',
                  reverseRelationName: 'relDummySubGrid',
                  multi: false
                }
              ]

            }

            const expectedModel = {
              label: 'Test',
              name: 'test',
              paths: {
                active: {
                  fieldName: 'active',
                  type: 'boolean',
                  validation: {
                    mandatory: false
                  }
                },
                pk: {
                  fieldName: 'pk',
                  type: 'serial',
                  validation: {}
                },
                relUser: {
                  multi: false,
                  relationName: 'relUser',
                  reverseRelationName: 'relDummySubGrid',
                  targetEntity: 'User',
                  type: 'relation'
                }
              }
            }

            const result = helpers.defaultModelTransformer(inputModel)
            expect(result).to.eql(expectedModel)
          })
        })

        describe('flattenObjectValues', () => {
          test('should extract value attributes of object values', () => {
            const values = {
              'relUser.pk': {value: '1', display: 'Tocco'},
              'firstname': 'Test'
            }

            const expectedResult = {
              'relUser.pk': '1',
              'firstname': 'Test'
            }

            const result = helpers.flattenObjectValues(values)
            expect(result).to.eql(expectedResult)
          })
        })

        describe('fetchPrincipal', () => {
          test('should call requestSaga and return the principals info object', () => {
            const response = {
              username: 'tocco',
              businessUnit: 'test1'
            }

            const expectedReturn = {
              username: 'tocco',
              currentBusinessUnit: 'test1'
            }

            return expectSaga(helpers.fetchPrincipal)
              .provide([
                [matchers.call.fn(requestSaga), {body: response}]
              ])
              .returns(expectedReturn)
              .run()
          })

          test('should load cached principal info object after fetch', async() => {
            const expectedReturn = {
              username: 'tocco',
              currentBusinessUnit: 'test1'
            }

            cache.addShortTerm('session', 'principal', expectedReturn)

            return expectSaga(helpers.fetchPrincipal)
              .returns(expectedReturn)
              .run()
          })
        })
      })

      describe('createSortingString', () => {
        test('handles empty list', () => {
          const results = helpers.createSortingString([])
          expect(results).to.be.empty
        })

        test('handles sorting fields', () => {
          const results = helpers.createSortingString([
            {
              field: 'first',
              order: 'asc'
            },
            {
              field: 'second',
              order: 'desc'
            }
          ])
          expect(results).to.equal('first asc, second desc')
        })
      })
    })
  })
})
<|endoftext|>


<commit_before>'use strict';

angular.module('mean.articles').controller('ArticlesController', ['$scope', '$stateParams', '$location', '$window', 'Global', 'Articles', 'Places', 'PlacesFromSpreadsheet', 'uiGmapGoogleMapApi',
  function($scope, $stateParams, $location, $window, Global, Articles, Places, PlacesFromSpreadsheet, GoogleMapApi) {
    $scope.global = Global;
	// map marker icons?!
	var icon_reddot = '/articles/assets/img/dot-red.png';
	//var icon_bluedot = '/articles/assets/img/dot-blue.png';

    $scope.hasAuthorization = function(article) {
      if (!article || !article.user) return false;
      return $scope.global.isAdmin || article.user._id === $scope.global.user._id;
    };

    $scope.create = function(isValid) {
      if (isValid) {
        var article = new Articles({
          title: this.title,
          content: this.content
        });
        article.$save(function(response) {
          $location.path('articles/' + response._id);
        });

        this.title = '';
        this.content = '';
      } else {
        $scope.submitted = true;
      }
    };

    $scope.remove = function(article) {
      if (article) {
        article.$remove(function(response) {
          for (var i in $scope.articles) {
            if ($scope.articles[i] === article) {
              $scope.articles.splice(i, 1);
            }
          }
          $location.path('articles');
        });
      } else {
        $scope.article.$remove(function(response) {
          $location.path('articles');
        });
      }
    };

    $scope.update = function(isValid) {
      if (isValid) {
        var article = $scope.article;
        if (!article.updated) {
          article.updated = [];
        }
        article.updated.push(new Date().getTime());

        article.$update(function() {
          $location.path('articles/' + article._id);
        });
      } else {
        $scope.submitted = true;
      }
    };

    $scope.find = function() {
		$scope.map = { center: { latitude: 45, longitude: -73 }, zoom: 8 };
      Articles.query(function(articles) {
        $scope.articles = articles;
      });
    };

    $scope.mainmap = function() {
		$scope.map = {center: {latitude: 32.95, longitude: -117 }, zoom: 10, bounds: {}, control: {}, markerControl: {} };
		$scope.options = {};//scrollwheel: false};
		$scope.coordsUpdates = 0;
		$scope.dynamicMoveCtr = 0;

		$scope.highlightPlace = $scope.highlightNumber = false;
		$scope.markers = [];
		//	Map Styles : not sure how to inject otherwise..
		$scope.styles = [{'featureType':'water','elementType':'all','stylers':[{'hue':'#e9ebed'},{'saturation':-78},{'lightness':67},{'visibility':'simplified'}]},{'featureType':'landscape','elementType':'all','stylers':[{'hue':'#ffffff'},{'saturation':-100},{'lightness':100},{'visibility':'simplified'}]},{'featureType':'road','elementType':'geometry','stylers':[{'hue':'#bbc0c4'},{'saturation':-93},{'lightness':31},{'visibility':'simplified'}]},{'featureType':'poi','elementType':'all','stylers':[{'hue':'#ffffff'},{'saturation':-100},{'lightness':100},{'visibility':'off'}]},{'featureType':'road.local','elementType':'geometry','stylers':[{'hue':'#e9ebed'},{'saturation':-90},{'lightness':-8},{'visibility':'simplified'}]},{'featureType':'transit','elementType':'all','stylers':[{'hue':'#e9ebed'},{'saturation':10},{'lightness':69},{'visibility':'on'}]},{'featureType':'administrative.locality','elementType':'all','stylers':[{'hue':'#2c2e33'},{'saturation':7},{'lightness':19},{'visibility':'on'}]},{'featureType':'road','elementType':'labels','stylers':[{'hue':'#bbc0c4'},{'saturation':-93},{'lightness':31},{'visibility':'on'}]},{'featureType':'road.arterial','elementType':'labels','stylers':[{'hue':'#bbc0c4'},{'saturation':-93},{'lightness':-2},{'visibility':'simplified'}]}];

		/**
         * SUCCESS!! GoogleMapApi is a promise with then callback of google.maps obj
         */
        GoogleMapApi.then(function(maps) {
			// trigger gmap.resize when windows resize
			var w = angular.element($window);
			w.bind('resize', function() {
				var gmapd = $scope.map.control.getGMap();
				maps.event.trigger( gmapd, 'resize' );
			});

			setTimeout(function() {
				var gmapd = $scope.map.control.getGMap();
				// on home screen, add some map listener(s)
				$scope.refilterTimeout = null;
				$scope.refilter = function() {
					var gmapd = $scope.map.control.getGMap();
					var bounds = gmapd.getBounds();
					// get the 2 boundary corners LatLng objects from the bounds
					// https://developers.google.com/maps/documentation/javascript/reference#LatLngBounds
					var ne = bounds.getNorthEast();
					var sw = bounds.getSouthWest();
					// combine in to 1 object to send to the server
					var bounds_corners = ne.toString() +','+ sw.toString();
					//console.log(':: map bounds update ::');
					//console.log(bounds_corners);
					//console.log(':: querying Places now?! ::');
					Places.query({
						articleId: bounds_corners
					}, function(newplaces) {
						//console.log('queried Places : got :');
						//console.log(places);
						var newmarkers = [];
						angular.forEach(newplaces, function( marker, k ) {
							// set our default icon here?!
							marker.icon = icon_reddot;

							newmarkers.push(marker);
						});
						$scope.markers = newmarkers;
						//console.log('-- markers : ');
						//console.log($scope.markers);
					});

					Places.query({
						articleId: 'findbad'
					}, function(newplaces) {
						//console.log('queried Places : got :');
						//console.log(places);
						var badmarkers = [];
						angular.forEach(newplaces, function( marker, k ) {
							// set our default icon here?!
							if ( !marker.hasOwnProperty('lat') || !marker.hasOwnProperty('lng') ) {
								badmarkers.push(marker);
							}
						});
						$scope.badMarkers = badmarkers;
						//console.log('-- markers : ');
						//console.log($scope.markers);
					});
				};
				// listen when the center has manually been moved
				$scope.mapMoveListener = maps.event.addListener(gmapd, 'center_changed', function() {
					clearTimeout( $scope.refilterTimeout );
					// add throttled call to filter map markers in the area
					$scope.refilterTimeout = setTimeout( $scope.refilter, 200 );
				});
				$scope.mapZoomListener = maps.event.addListener(gmapd, 'zoom_changed', function() {
					clearTimeout( $scope.refilterTimeout );
					// add throttled call to filter map markers in the area
					$scope.refilterTimeout = setTimeout( $scope.refilter, 200 );
				});
				// initial check for markers in the area, too
				$scope.refilterTimeout = setTimeout( $scope.refilter, 100 );
			}, 400);
		});

		// set up our click / mouse events?
		$scope.markerClick = function( result, event ) {
			// can't figure out how to run a filter soooooooo
			var clickedon = false;
			angular.forEach( $scope.markers, function( item, n ) {
				if ( item.id === result.key ) {
					clickedon = item;
					$scope.highlightNumber = n;
				}
			});
			$scope.highlightPlace = clickedon;
			console.log('clicked #'+ $scope.highlightNumber +' = _id: '+ result.key +' : '+ clickedon.fullName);
			// #todo : now somehow social factory to get couple feed items from server?!
		};
		$scope.markerMouseOver = function( result, event ) {
			// in case we want to change color on rollover or do something
			//console.log('markerMouseOver '+ result.key);
		};
		$scope.markerMouseOut = function( result, event ) {
			// the opposite of mouseover
			//console.log('markerMouseOut '+ result.key);
		};
		$scope.clickEventsObject = {
			click: $scope.markerClick,
			mouseover: $scope.markerMouseOver,
			mouseout: $scope.markerMouseOut,
		};
    };

    $scope.findOne = function() {
      Articles.get({
        articleId: $stateParams.articleId
      }, function(article) {
        $scope.article = article;
      });
    };
  }
]);
<commit_msg>add sample chunk for geocoding some addresses that lost their lat & lng in the db shuffle<commit_after>'use strict';

angular.module('mean.articles').controller('ArticlesController', ['$scope', '$stateParams', '$location', '$window', 'Global', 'Articles', 'Places', 'PlacesFromSpreadsheet', 'uiGmapGoogleMapApi',
  function($scope, $stateParams, $location, $window, Global, Articles, Places, PlacesFromSpreadsheet, GoogleMapApi) {
    $scope.global = Global;
	// map marker icons?!
	var icon_reddot = '/articles/assets/img/dot-red.png';
	//var icon_bluedot = '/articles/assets/img/dot-blue.png';

    $scope.hasAuthorization = function(article) {
      if (!article || !article.user) return false;
      return $scope.global.isAdmin || article.user._id === $scope.global.user._id;
    };

    $scope.create = function(isValid) {
      if (isValid) {
        var article = new Articles({
          title: this.title,
          content: this.content
        });
        article.$save(function(response) {
          $location.path('articles/' + response._id);
        });

        this.title = '';
        this.content = '';
      } else {
        $scope.submitted = true;
      }
    };

    $scope.remove = function(article) {
      if (article) {
        article.$remove(function(response) {
          for (var i in $scope.articles) {
            if ($scope.articles[i] === article) {
              $scope.articles.splice(i, 1);
            }
          }
          $location.path('articles');
        });
      } else {
        $scope.article.$remove(function(response) {
          $location.path('articles');
        });
      }
    };

    $scope.update = function(isValid) {
      if (isValid) {
        var article = $scope.article;
        if (!article.updated) {
          article.updated = [];
        }
        article.updated.push(new Date().getTime());

        article.$update(function() {
          $location.path('articles/' + article._id);
        });
      } else {
        $scope.submitted = true;
      }
    };

    $scope.find = function() {
		$scope.map = { center: { latitude: 45, longitude: -73 }, zoom: 8 };
      Articles.query(function(articles) {
        $scope.articles = articles;
      });
    };

    $scope.mainmap = function() {
		$scope.map = {center: {latitude: 32.95, longitude: -117 }, zoom: 10, bounds: {}, control: {}, markerControl: {} };
		$scope.options = {};//scrollwheel: false};
		$scope.coordsUpdates = 0;
		$scope.dynamicMoveCtr = 0;

		$scope.highlightPlace = $scope.highlightNumber = false;
		$scope.markers = [];
		//	Map Styles : not sure how to inject otherwise..
		$scope.styles = [{'featureType':'water','elementType':'all','stylers':[{'hue':'#e9ebed'},{'saturation':-78},{'lightness':67},{'visibility':'simplified'}]},{'featureType':'landscape','elementType':'all','stylers':[{'hue':'#ffffff'},{'saturation':-100},{'lightness':100},{'visibility':'simplified'}]},{'featureType':'road','elementType':'geometry','stylers':[{'hue':'#bbc0c4'},{'saturation':-93},{'lightness':31},{'visibility':'simplified'}]},{'featureType':'poi','elementType':'all','stylers':[{'hue':'#ffffff'},{'saturation':-100},{'lightness':100},{'visibility':'off'}]},{'featureType':'road.local','elementType':'geometry','stylers':[{'hue':'#e9ebed'},{'saturation':-90},{'lightness':-8},{'visibility':'simplified'}]},{'featureType':'transit','elementType':'all','stylers':[{'hue':'#e9ebed'},{'saturation':10},{'lightness':69},{'visibility':'on'}]},{'featureType':'administrative.locality','elementType':'all','stylers':[{'hue':'#2c2e33'},{'saturation':7},{'lightness':19},{'visibility':'on'}]},{'featureType':'road','elementType':'labels','stylers':[{'hue':'#bbc0c4'},{'saturation':-93},{'lightness':31},{'visibility':'on'}]},{'featureType':'road.arterial','elementType':'labels','stylers':[{'hue':'#bbc0c4'},{'saturation':-93},{'lightness':-2},{'visibility':'simplified'}]}];

		/**
         * SUCCESS!! GoogleMapApi is a promise with then callback of google.maps obj
         */
        GoogleMapApi.then(function(maps) {
			// trigger gmap.resize when windows resize
			var w = angular.element($window);
			w.bind('resize', function() {
				var gmapd = $scope.map.control.getGMap();
				maps.event.trigger( gmapd, 'resize' );
			});

			setTimeout(function() {
				var gmapd = $scope.map.control.getGMap();
				// on home screen, add some map listener(s)
				$scope.refilterTimeout = null;
				$scope.refilter = function() {
					var gmapd = $scope.map.control.getGMap();
					var bounds = gmapd.getBounds();
					// get the 2 boundary corners LatLng objects from the bounds
					// https://developers.google.com/maps/documentation/javascript/reference#LatLngBounds
					var ne = bounds.getNorthEast();
					var sw = bounds.getSouthWest();
					// combine in to 1 object to send to the server
					var bounds_corners = ne.toString() +','+ sw.toString();
					//console.log(':: map bounds update ::');
					//console.log(bounds_corners);
					//console.log(':: querying Places now?! ::');
					Places.query({
						articleId: bounds_corners
					}, function(newplaces) {
						//console.log('queried Places : got :');
						//console.log(places);
						var newmarkers = [];
						angular.forEach(newplaces, function( marker, k ) {
							// set our default icon here?!
							marker.icon = icon_reddot;

							newmarkers.push(marker);
						});
						$scope.markers = newmarkers;
						//console.log('-- markers : ');
						//console.log($scope.markers);
					});
				};
				// listen when the center has manually been moved
				$scope.mapMoveListener = maps.event.addListener(gmapd, 'center_changed', function() {
					clearTimeout( $scope.refilterTimeout );
					// add throttled call to filter map markers in the area
					$scope.refilterTimeout = setTimeout( $scope.refilter, 200 );
				});
				$scope.mapZoomListener = maps.event.addListener(gmapd, 'zoom_changed', function() {
					clearTimeout( $scope.refilterTimeout );
					// add throttled call to filter map markers in the area
					$scope.refilterTimeout = setTimeout( $scope.refilter, 200 );
				});
				// initial check for markers in the area, too
				$scope.refilterTimeout = setTimeout( $scope.refilter, 100 );

				Places.query({
					articleId: 'findbad'
				}, function(newplaces) {
					//console.log('queried Places : got :');
					//console.log(places);
					var badmarkers = [];

					angular.forEach(newplaces, function( marker, k ) {
						// set our default icon here?!
						if ( !marker.hasOwnProperty('lat') || !marker.hasOwnProperty('lng') ) {
							badmarkers.push(marker);
						}
					});
					$scope.badMarkers = badmarkers;
					if ( badmarkers.length > 0 ) {
						placeCheckStep();
					}
					//console.log('-- markers : ');
					//console.log($scope.markers);
				});
			}, 400);

			var geocoder = new maps.Geocoder();
			function placeCheckStep() {
				if ( $scope.badMarkers.length > 0 ) {
					var marker = $scope.badMarkers.shift();
					var addr = marker.fullAddr;
					if ( addr === ', ,  ' ) {
						console.log('address totally blank for '+ marker.name +' '+ marker.sublocation);
						// and repeat
						setTimeout(placeCheckStep, 300);
					} else {
						console.log('geocoding for '+ marker.name + ' (which was missing its lat lng in the db) : ' + addr);
						geocoder.geocode( { 'address': addr }, function(results, status) {
							if (status == google.maps.GeocoderStatus.OK) {
								var newlatlng = results[0].geometry.location;
								console.log(newlatlng.toString());
								marker.lat = newlatlng.lat();
								marker.lng = newlatlng.lng();
								//marker.$update(
							} else {
								console.log('Geocode was not successful for the following reason: ' + status);
							}
							// and repeat
							setTimeout(placeCheckStep, 300);
						});
					}
				}
			}
		});

		// set up our click / mouse events?
		$scope.markerClick = function( result, event ) {
			// can't figure out how to run a filter soooooooo
			var clickedon = false;
			angular.forEach( $scope.markers, function( item, n ) {
				if ( item.id === result.key ) {
					clickedon = item;
					$scope.highlightNumber = n;
				}
			});
			$scope.highlightPlace = clickedon;
			console.log('clicked #'+ $scope.highlightNumber +' = _id: '+ result.key +' : '+ clickedon.fullName);
			// #todo : now somehow social factory to get couple feed items from server?!
		};
		$scope.markerMouseOver = function( result, event ) {
			// in case we want to change color on rollover or do something
			//console.log('markerMouseOver '+ result.key);
		};
		$scope.markerMouseOut = function( result, event ) {
			// the opposite of mouseover
			//console.log('markerMouseOut '+ result.key);
		};
		$scope.clickEventsObject = {
			click: $scope.markerClick,
			mouseover: $scope.markerMouseOver,
			mouseout: $scope.markerMouseOut,
		};
    };

    $scope.findOne = function() {
      Articles.get({
        articleId: $stateParams.articleId
      }, function(article) {
        $scope.article = article;
      });
    };
  }
]);
<|endoftext|>


<commit_before>import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import Playground from 'component-playground';
import {
    Checklist,
    Dropdown,
    Graph,
    Input,
    RadioItems,
    RangeSlider,
    Slider,
    SyntaxHighlighter
} from '../src';

const SyntaxHighlighterExample = `

const properties = {
    language: 'python',
    theme: 'light',
    customStyle: {},
    codeTagProps: {},
    useInlineStyles: true,
    showLineNumbers: false,
    startingLineNumber: 0,
    lineNumberContainerStyle: {},
    lineNumberStyle: {},
    wrapLines: false,
    lineStyle: {},
    children: \`import Dash
dash.layout = Div([
    Graph(figure={'data': [{'x': [1, 2, 3]}]})
]);

def update_graph(lahlah):
	return dict(updates=lahlah)

function thisIsJavascript(test) {
    console.log({test: that});
}
\`
}

ReactDOM.render(<SyntaxHighlighter {...properties}/>, mountNode);
`

const DropdownExample = `
const properties = {
    id: 'my dropdown',
    disabled: false,
    multi: false,
    options: [
        {'label': 'Melons', 'value': 'melons', 'disabled': false},
        {'label': 'Apples', 'value': 'apples'},
        {'label': 'Oranges', 'value': 'oranges', 'disabled': true}
    ]
};

ReactDOM.render(<Dropdown {...properties}/>, mountNode);`

const GraphExample = `
const properties = {
    id: 'my graph',
    figure: {
        data: [
            {'x': [1, 2, 3], 'y': [4, 1, 6]}
        ],
        layout: {
            title: 'Graph Component'
        }
    }
};

ReactDOM.render(<Graph {...properties}/>, mountNode);`

const SliderExample = `
const properties = {
    className: 'my-slider',
    disabled: false,
    dots: false,
    id: 'my-slider',
    included: false,
    marks: {},
    min: -5,
    max: 5,
    step: 0.5,
    labels: {},
    value: -3,
    vertical: false
};

ReactDOM.render(<Slider {...properties}/>, mountNode);`

const RangeSliderExample = `
const properties = {
    allowCross: false,
    className: 'my-range-slider',
    count: 2,
    disabled: false,
    dots: false,
    id: 'my-range-slider',
    included: false,
    marks: {},
    min: -5,
    max: 5,
    pushable: false,
    step: 0.5,
    labels: {},
    vertical: false
};

class Controller extends Component {
    constructor() {
        super();
        this.state = {
            value: [1, 2]
        };
    }

    render() {
        return (<RangeSlider
            valueChanged={(props) => {
                this.setState(props);
            }}
            fireEvent={event => console.warn(event)}
            value={this.state.value}
            {...properties}
        />);
    }
}

ReactDOM.render(<Controller/>, mountNode);`

const InputExample = `
const properties = {
    className: 'my-input-wrapper',
    id: 'my-input',
    placeholder: 'Enter a value....',
    style: {'color': 'skyblue'},
    value: 'Initial Value'
};

ReactDOM.render(<Input {...properties}/>, mountNode);`

const RadioExample = `
const properties = {
    id: 'my radios',
    labelStyle: {'display': 'block'},
    disabled: false,
    options: [
        {'label': 'Melons', 'value': 'melons', 'disabled': false},
        {'label': 'Apples', 'value': 'apples'},
        {'label': 'Oranges', 'value': 'oranges', 'disabled': true}
    ],
    value: 'apples'
};

ReactDOM.render(<RadioItems {...properties}/>, mountNode);`

const ChecklistExample = `
const properties = {
    id: 'my checklist',
    labelStyle: {'display': 'block'},
    disabled: false,
    options: [
        {'label': 'Melons', 'value': 'melons', 'disabled': false},
        {'label': 'Apples', 'value': 'apples'},
        {'label': 'Oranges', 'value': 'oranges', 'disabled': true}
    ]
};

class Controller extends Component {
    constructor() {
        super();
        this.state = {
            values: ['melons', 'apples']
        };
    }

    render() {
        return (<Checklist
            valueChanged={(props) => {
                this.setState(props);
            }}
            fireEvent={event => console.warn(event)}
            values={this.state.values}
            {...properties}
        />);
    }
}

ReactDOM.render(<Controller/>, mountNode);`

const examples = [
    {name: 'SyntaxHighlighter', code: SyntaxHighlighterExample},
    {name: 'Radio', code: RadioExample},
    {name: 'Checklist', code: ChecklistExample},
    {name: 'Dropdown', code: DropdownExample},
    {name: 'Slider', code: SliderExample},
    {name: 'RangeSlider', code: RangeSliderExample},
    {name: 'Input', code: InputExample},
    {name: 'Graph', code: GraphExample}
];

class Demo extends Component {
    render() {
        return (

Dash Core Component Suite Demo

                {examples.map(example =>

{example.name}

                            <Playground
                                codeText={example.code}
                                scope={{Component, React, ReactDOM, Checklist, Dropdown, Graph, Input, RadioItems, RangeSlider, Slider, SyntaxHighlighter}}
                                noRender={false}
                                theme={'xq-light'}
                            />

                    )}

        );
    }
}

export default Demo;
<commit_msg>extend demo to include graph events<commit_after>import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import Playground from 'component-playground';
import {
    Checklist,
    Dropdown,
    Graph,
    Input,
    RadioItems,
    RangeSlider,
    Slider,
    SyntaxHighlighter
} from '../src';

const SyntaxHighlighterExample = `

const properties = {
    language: 'python',
    theme: 'light',
    customStyle: {},
    codeTagProps: {},
    useInlineStyles: true,
    showLineNumbers: false,
    startingLineNumber: 0,
    lineNumberContainerStyle: {},
    lineNumberStyle: {},
    wrapLines: false,
    lineStyle: {},
    children: \`import Dash
dash.layout = Div([
    Graph(figure={'data': [{'x': [1, 2, 3]}]})
]);

def update_graph(lahlah):
	return dict(updates=lahlah)

function thisIsJavascript(test) {
    console.log({test: that});
}
\`
}

ReactDOM.render(<SyntaxHighlighter {...properties}/>, mountNode);
`

const DropdownExample = `
const properties = {
    id: 'my dropdown',
    disabled: false,
    multi: false,
    options: [
        {'label': 'Melons', 'value': 'melons', 'disabled': false},
        {'label': 'Apples', 'value': 'apples'},
        {'label': 'Oranges', 'value': 'oranges', 'disabled': true}
    ]
};

ReactDOM.render(<Dropdown {...properties}/>, mountNode);`

const GraphExample = `
const properties = {
    id: 'my graph',
    figure: {
        data: [
            {'x': [1, 2, 3], 'y': [4, 1, 6]}
        ],
        layout: {
            title: 'Graph Component'
        }
    }
};

class Controller extends Component {
    constructor() {
        super();
        this.state = {};
    }

    render() {
        return (

            <Graph
                valueChanged={(props) => {
                    this.setState({props});
                }}
                fireEvent={event => {
                    this.setState({event})
                }}
                {...properties}
            />

{JSON.stringify(this.state, null, 2)}

);
    }
}

ReactDOM.render(<Controller/>, mountNode);`

const SliderExample = `
const properties = {
    className: 'my-slider',
    disabled: false,
    dots: false,
    id: 'my-slider',
    included: false,
    marks: {},
    min: -5,
    max: 5,
    step: 0.5,
    labels: {},
    value: -3,
    vertical: false
};

ReactDOM.render(<Slider {...properties}/>, mountNode);`

const RangeSliderExample = `
const properties = {
    allowCross: false,
    className: 'my-range-slider',
    count: 2,
    disabled: false,
    dots: false,
    id: 'my-range-slider',
    included: false,
    marks: {},
    min: -5,
    max: 5,
    pushable: false,
    step: 0.5,
    labels: {},
    vertical: false
};

class Controller extends Component {
    constructor() {
        super();
        this.state = {
            value: [1, 2]
        };
    }

    render() {
        return (<RangeSlider
            valueChanged={(props) => {
                this.setState(props);
            }}
            fireEvent={event => console.warn(event)}
            value={this.state.value}
            {...properties}
        />);
    }
}

ReactDOM.render(<Controller/>, mountNode);`

const InputExample = `
const properties = {
    className: 'my-input-wrapper',
    id: 'my-input',
    placeholder: 'Enter a value....',
    style: {'color': 'skyblue'},
    value: 'Initial Value'
};

ReactDOM.render(<Input {...properties}/>, mountNode);`

const RadioExample = `
const properties = {
    id: 'my radios',
    labelStyle: {'display': 'block'},
    disabled: false,
    options: [
        {'label': 'Melons', 'value': 'melons', 'disabled': false},
        {'label': 'Apples', 'value': 'apples'},
        {'label': 'Oranges', 'value': 'oranges', 'disabled': true}
    ],
    value: 'apples'
};

ReactDOM.render(<RadioItems {...properties}/>, mountNode);`

const ChecklistExample = `
const properties = {
    id: 'my checklist',
    labelStyle: {'display': 'block'},
    disabled: false,
    options: [
        {'label': 'Melons', 'value': 'melons', 'disabled': false},
        {'label': 'Apples', 'value': 'apples'},
        {'label': 'Oranges', 'value': 'oranges', 'disabled': true}
    ]
};

class Controller extends Component {
    constructor() {
        super();
        this.state = {
            values: ['melons', 'apples']
        };
    }

    render() {
        return (<Checklist
            valueChanged={(props) => {
                this.setState(props);
            }}
            fireEvent={event => console.warn(event)}
            values={this.state.values}
            {...properties}
        />);
    }
}

ReactDOM.render(<Controller/>, mountNode);`

const examples = [
    {name: 'Graph', code: GraphExample},
    {name: 'SyntaxHighlighter', code: SyntaxHighlighterExample},
    {name: 'Radio', code: RadioExample},
    {name: 'Checklist', code: ChecklistExample},
    {name: 'Dropdown', code: DropdownExample},
    {name: 'Slider', code: SliderExample},
    {name: 'RangeSlider', code: RangeSliderExample},
    {name: 'Input', code: InputExample}
];

class Demo extends Component {
    render() {
        return (

Dash Core Component Suite Demo

                {examples.map(example =>

{example.name}

                            <Playground
                                codeText={example.code}
                                scope={{Component, React, ReactDOM, Checklist, Dropdown, Graph, Input, RadioItems, RangeSlider, Slider, SyntaxHighlighter}}
                                noRender={false}
                                theme={'xq-light'}
                            />

                    )}

        );
    }
}

export default Demo;
<|endoftext|>


<commit_before>const path = require("path");
const webpack = require("webpack");
const merge = require("webpack-merge");
const WriteFilePlugin = require("write-file-webpack-plugin");

const commonConfig = require("./webpack.config-common.js");

module.exports = merge(commonConfig, {
  module: {
    rules: [
      {
        test: /\.(js)/,
        include: path.resolve("lib"),
        loader: "istanbul-instrumenter-loader"
      },
      {
        test: /\.js$/,
        loader: "babel-loader",
        query: {
          presets: [["babel-preset-env", { targets: { node: "6.14" } }]],
          plugins: ["transform-object-rest-spread", "transform-runtime"]
        },
        include: [
          path.resolve(__dirname, "..", "lib"),
          path.resolve(__dirname, "..", "test")
        ]
      }
    ]
  },

  plugins: [
    new webpack.DefinePlugin({
      "process.env.NODE_ENV": JSON.stringify("test")
    }),

    new WriteFilePlugin()
  ]
});
<commit_msg>Set debugger webpack test config to development mode<commit_after>const path = require("path");
const webpack = require("webpack");
const merge = require("webpack-merge");
const WriteFilePlugin = require("write-file-webpack-plugin");

const commonConfig = require("./webpack.config-common.js");

module.exports = merge(commonConfig, {
  mode: "development",
  module: {
    rules: [
      {
        test: /\.(js)/,
        include: path.resolve("lib"),
        loader: "istanbul-instrumenter-loader"
      },
      {
        test: /\.js$/,
        loader: "babel-loader",
        query: {
          presets: [["babel-preset-env", { targets: { node: "6.14" } }]],
          plugins: ["transform-object-rest-spread", "transform-runtime"]
        },
        include: [
          path.resolve(__dirname, "..", "lib"),
          path.resolve(__dirname, "..", "test")
        ]
      }
    ]
  },

  plugins: [
    new webpack.DefinePlugin({
      "process.env.NODE_ENV": JSON.stringify("test")
    }),

    new WriteFilePlugin()
  ]
});
<|endoftext|>


<commit_before>import combineRules from '../combineRules'

describe('Combining rules', () => {
  it('should create a combined rule', () => {
    const rule = props => ({
      color: 'red',
      fontSize: props.fontSize,
      lineHeight: props.lineHeight,
      padding: 10,
    })

    const anotherRule = props => ({
      backgroundColor: 'blue',
      lineHeight: props.lineHeight * 2,
      padding: 20,
    })

    const combinedRule = combineRules(rule, anotherRule)

    expect(
      combinedRule({
        fontSize: 12,
        lineHeight: 10,
      })
    ).toEqual({
      color: 'red',
      backgroundColor: 'blue',
      fontSize: 12,
      lineHeight: 20,
      padding: 20,
    })
  })

  it('should combine any amount of rules', () => {
    const rule1 = props => ({
      color: 'red',
      fontSize: props.fontSize,
      lineHeight: props.lineHeight,
      padding: 10,
    })

    const rule2 = props => ({
      backgroundColor: 'blue',
      lineHeight: props.lineHeight * 2,
      padding: 20,
    })

    const rule3 = props => ({
      color: props.color,
    })

    const rule4 = () => ({
      display: 'flex',
    })

    const combinedRule = combineRules(rule1, rule2, rule3, rule4)

    expect(
      combinedRule({
        fontSize: 12,
        lineHeight: 10,
        color: 'green',
      })
    ).toEqual({
      color: 'green',
      display: 'flex',
      backgroundColor: 'blue',
      fontSize: 12,
      lineHeight: 20,
      padding: 20,
    })
  })

  it.only('should merge _className as a special key', () => {
    const rule = () => ({
      _className: 'foo bar',
      color: 'red',
      padding: 10,
    })

    const anotherRule = props => ({
      _className: props.cls,
      backgroundColor: 'blue',
      padding: 20,
    })

    const combinedRule = combineRules(rule, anotherRule)

    expect(
      combinedRule({
        cls: 'baz',
      })
    ).toEqual({
      _className: 'foo bar baz',
      color: 'red',
      backgroundColor: 'blue',
      padding: 20,
    })
  })
})
<commit_msg>Update packages/fela/src/__tests__/combineRules-test.js<commit_after>import combineRules from '../combineRules'

describe('Combining rules', () => {
  it('should create a combined rule', () => {
    const rule = props => ({
      color: 'red',
      fontSize: props.fontSize,
      lineHeight: props.lineHeight,
      padding: 10,
    })

    const anotherRule = props => ({
      backgroundColor: 'blue',
      lineHeight: props.lineHeight * 2,
      padding: 20,
    })

    const combinedRule = combineRules(rule, anotherRule)

    expect(
      combinedRule({
        fontSize: 12,
        lineHeight: 10,
      })
    ).toEqual({
      color: 'red',
      backgroundColor: 'blue',
      fontSize: 12,
      lineHeight: 20,
      padding: 20,
    })
  })

  it('should combine any amount of rules', () => {
    const rule1 = props => ({
      color: 'red',
      fontSize: props.fontSize,
      lineHeight: props.lineHeight,
      padding: 10,
    })

    const rule2 = props => ({
      backgroundColor: 'blue',
      lineHeight: props.lineHeight * 2,
      padding: 20,
    })

    const rule3 = props => ({
      color: props.color,
    })

    const rule4 = () => ({
      display: 'flex',
    })

    const combinedRule = combineRules(rule1, rule2, rule3, rule4)

    expect(
      combinedRule({
        fontSize: 12,
        lineHeight: 10,
        color: 'green',
      })
    ).toEqual({
      color: 'green',
      display: 'flex',
      backgroundColor: 'blue',
      fontSize: 12,
      lineHeight: 20,
      padding: 20,
    })
  })

  it('should merge _className as a special key', () => {
    const rule = () => ({
      _className: 'foo bar',
      color: 'red',
      padding: 10,
    })

    const anotherRule = props => ({
      _className: props.cls,
      backgroundColor: 'blue',
      padding: 20,
    })

    const combinedRule = combineRules(rule, anotherRule)

    expect(
      combinedRule({
        cls: 'baz',
      })
    ).toEqual({
      _className: 'foo bar baz',
      color: 'red',
      backgroundColor: 'blue',
      padding: 20,
    })
  })
})
<|endoftext|>


<commit_before>import styled from 'styled-components'
import {
  StyledInputCss,
  StyledEditableWrapperCss,
  Button,
  theme
} from 'tocco-ui'
import {StyledButton} from 'tocco-ui/src/Button'
import {StyledButtonGroup} from 'tocco-ui/src/ButtonGroup'

export const StyledLoginFormInput = styled.input`
  @keyframes onAutoFillStart { from {} }

  && {
    transition: background-color 50000s, color 50000s, filter 50000s;
    &:-webkit-autofill {
      animation-duration: 50000s;
      animation-name: onAutoFillStart;
    }
    padding-top: .7rem;
    padding-bottom: .7rem;
  }

  ${StyledInputCss}
`

export const StyledLoginFormInputWrapper = styled.div`
  && {
    ${StyledEditableWrapperCss}
  }
`

export const StyledLoginButton = styled(Button)`
  &&& {
    border-radius: ${theme.radii('large')};
    display: flex;
    justify-content: center;
    padding: 1.25rem 0;
    width:100%;
    margin: 20px 0;

    &:not(:last-child) {
      margin-right: 0;
    }

  }
`

export const StyledTransparentButton = styled(Button)`
  &&& {
    margin-right: 0;
    background-color: transparent;
    color: ${theme.color('primary')};
    padding: 1.25rem 0;
    width:100%;
    justify-content: center;

    &:hover {
      background-color: transparent;
    }
  }
`

export const StyledLoginFormWrapper = styled.div`
  ${StyledButtonGroup} {
    margin-top: 1.8rem;
    width: auto;
    flex-direction: column;

    ${StyledButton} {
      flex-grow: 1;

      > span {
        width: 100%;
      }
    }
  }
`
<commit_msg>refactor(login): adjust button style<commit_after>import styled from 'styled-components'
import {
  StyledInputCss,
  StyledEditableWrapperCss,
  Button,
  theme,
  scale
} from 'tocco-ui'
import {StyledButton} from 'tocco-ui/src/Button'
import {StyledButtonGroup} from 'tocco-ui/src/ButtonGroup'

export const StyledLoginFormInput = styled.input`
  @keyframes onAutoFillStart { from {} }

  && {
    transition: background-color 50000s, color 50000s, filter 50000s;
    &:-webkit-autofill {
      animation-duration: 50000s;
      animation-name: onAutoFillStart;
    }
    padding-top: .7rem;
    padding-bottom: .7rem;
  }

  ${StyledInputCss}
`

export const StyledLoginFormInputWrapper = styled.div`
  && {
    ${StyledEditableWrapperCss}
  }
`

export const StyledLoginButton = styled(Button)`
  &&& {
    font-size: ${scale.font(1.3)};
    border-radius: ${theme.radii('large')};
    display: flex;
    justify-content: center;
    padding: 1.25rem 0;
    width:100%;
    margin-top: 20px;
    margin-bottom: 10px;

    &:not(:last-child) {
      margin-right: 0;
    }
  }
`

export const StyledTransparentButton = styled(Button)`
  &&& {
    margin-right: 0;
    background-color: transparent;
    color: ${theme.color('primary')};
    padding: 1.25rem 0;
    width:100%;
    justify-content: center;

    &:hover {
      background-color: transparent;
    }
  }
`

export const StyledLoginFormWrapper = styled.div`
  ${StyledButtonGroup} {
    margin-top: 1.8rem;
    width: auto;
    flex-direction: column;

    ${StyledButton} {
      flex-grow: 1;

      > span {
        width: 100%;
      }
    }
  }
`
<|endoftext|>


<commit_before>'use strict';

const VALIDATOR_ARG_ERROR_MESSAGE =
  'The typeValidator argument must be a function ' +
  'with the signature function(props, propName, componentName).';

const MESSAGE_ARG_ERROR_MESSAGE =
  'The error message is optional, but must be a string if provided.';

const propIsRequired = (condition, props, propName, componentName) => {
  if (typeof condition === 'boolean') {
    return condition;
  } else if (typeof condition === 'function') {
    return condition(props, propName, componentName);
  } else if (Boolean(condition) === true) {
    return Boolean(condition);
  }

  return false;
};

const propExists = (props, propName) => props.hasOwnProperty(propName);

const missingPropError = (props, propName, componentName, message) => {
  if (Boolean(message)) {
    return new Error(message);
  }

  return new Error(
    `Required ${props[propName]} \`${propName}\`` +
    ` was not specified in \`${componentName}\`.`
  );
};

const guardAgainstInvalidArgTypes = (typeValidator, message) => {
  if (typeof typeValidator !== 'function') {
    throw new TypeError(VALIDATOR_ARG_ERROR_MESSAGE);
  }

  if (Boolean(message) && typeof message !== 'string') {
    throw new TypeError(MESSAGE_ARG_ERROR_MESSAGE);
  }
};

const isRequiredIf = (typeValidator, condition, message) => {
  guardAgainstInvalidArgTypes(typeValidator, message);

  return (props, propName, componentName) => {
    if (propIsRequired(condition, props, propName, componentName)) {
      if (propExists(props, propName)) {
        return typeValidator(props, propName, componentName);
      }

      return missingPropError(props, propName, componentName, message);
    }

    // Is not required, so just run typeValidator.
    return typeValidator(props, propName, componentName);
  };
};

export default isRequiredIf;
<commit_msg>Fix dont call prototypes warning<commit_after>'use strict';

const VALIDATOR_ARG_ERROR_MESSAGE =
  'The typeValidator argument must be a function ' +
  'with the signature function(props, propName, componentName).';

const MESSAGE_ARG_ERROR_MESSAGE =
  'The error message is optional, but must be a string if provided.';

const propIsRequired = (condition, props, propName, componentName) => {
  if (typeof condition === 'boolean') {
    return condition;
  } else if (typeof condition === 'function') {
    return condition(props, propName, componentName);
  } else if (Boolean(condition) === true) {
    return Boolean(condition);
  }

  return false;
};

const propExists = (props, propName) => props.hasOwnProperty(propName);

const missingPropError = (props, propName, componentName, message) => {
  if (Boolean(message)) {
    return new Error(message);
  }

  return new Error(
    `Required ${props[propName]} \`${propName}\`` +
    ` was not specified in \`${componentName}\`.`
  );
};

const guardAgainstInvalidArgTypes = (typeValidator, message) => {
  if (typeof typeValidator !== 'function') {
    throw new TypeError(VALIDATOR_ARG_ERROR_MESSAGE);
  }

  if (Boolean(message) && typeof message !== 'string') {
    throw new TypeError(MESSAGE_ARG_ERROR_MESSAGE);
  }
};

const isRequiredIf = (typeValidator, condition, message) => {
  guardAgainstInvalidArgTypes(typeValidator, message);

  return (props, propName, componentName, ...rest) => {
    if (propIsRequired(condition, props, propName, componentName)) {
      if (propExists(props, propName)) {
        return typeValidator(props, propName, componentName, ...rest);
      }

      return missingPropError(props, propName, componentName, message);
    }

    // Is not required, so just run typeValidator.
    return typeValidator(props, propName, componentName, ...rest);
  };
};

export default isRequiredIf;
<|endoftext|>


<commit_before><commit_msg>Update virtualsky.min.js<commit_after><|endoftext|>


<commit_before>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { Redirect } from 'react-router-dom';
import ScrollEvent from 'react-onscroll';

import Header from '../header/header';
import Footer from '../footer/footer';
import Map from '../map/map';
import WidgetTreeCover from '../widget-tree-cover/widget-tree-cover';
import WidgetTreeLocated from '../widget-tree-located/widget-tree-located';
import WidgetTreeLoss from '../widget-tree-loss/widget-tree-loss';
import WidgetTreeCoverLossAreas from '../widget-tree-cover-loss-areas/widget-tree-cover-loss-areas';
import WidgetAreasMostCoverGain from '../widget-areas-most-cover-gain/widget-areas-most-cover-gain';
import WidgetTotalAreaPlantations from '../widget-total-area-plantations/widget-total-area-plantations';
import WidgetTreeCoverGain from '../widget-tree-cover-gain/widget-tree-cover-gain';
import WidgetPlantationArea from '../widget-plantation-area/widget-plantation-area';
import WidgetStories from '../widget-stories/widget-stories';

class Root extends PureComponent {
  componentDidMount() {
    const { setInitialData } = this.props;
    setInitialData(this.props);
  }
  componentWillUpdate(nextProps) {
    const { iso, refreshCountryData, checkLoadingStatus } = this.props;
    if (iso !== nextProps.iso) {
      refreshCountryData(nextProps);
    } else {
      checkLoadingStatus(nextProps);
    }
  }

  handleScrollCallback() {
    // setPositionPage
   if (window.scrollY >= 59) {
     this.props.setPositionMap(true);
     this.props.setTopMap(0);
   }
   if(window.scrollY >= (document.getElementById('c-widget-stories').offsetTop - window.innerHeight)) {
     this.props.setPositionMap(false);
     this.props.setTopMap(document.getElementById('c-widget-stories').offsetTop - window.innerHeight);
   }
   if (window.scrollY < 59) {
     this.props.setPositionMap(false);
     this.props.setTopMap(59);
   }
 }

  render() {
    const { isLoading, topPage } = this.props;
    if (isLoading) {
      return (

Loading...

)
    } else {
      if(!topPage) {
        window.scrollTo(0, 0);
        this.props.setPositionPage(true);
      }
      return (

          <ScrollEvent handleScrollCallback={() => this.handleScrollCallback()} />

          <div className={`l-country__map ${this.props.fixed ? '-fixed' : ''}`} style={{top: this.props.topMap}}>
            <Map
              maxZoom={14}
              minZoom={3}
              mapOptions={{
                mapTypeId: 'grayscale',
                backgroundColor: '#99b3cc',
                disableDefaultUI: true,
                panControl: false,
                zoomControl: false,
                mapTypeControl: false,
                scaleControl: true,
                streetViewControl: false,
                overviewMapControl: false,
                tilt: 0,
                scrollwheel: false
              }} />

              <WidgetTreeCover />

              <WidgetTreeLocated />

              <WidgetTreeLoss />

              <WidgetTreeCoverLossAreas />

              <WidgetTreeCoverGain />

              <WidgetAreasMostCoverGain />

              <WidgetTotalAreaPlantations />

              <WidgetPlantationArea />

          <WidgetStories />

      )
    }
  }
}

Root.propTypes = {
  isLoading: PropTypes.bool.isRequired,
  iso: PropTypes.string.isRequired,
  countryRegion: PropTypes.number.isRequired,
  countryData: PropTypes.object.isRequired,
  countryRegions: PropTypes.array.isRequired,
  countriesList: PropTypes.array.isRequired,
  setInitialData: PropTypes.func.isRequired,
  refreshCountryData: PropTypes.func.isRequired,
  checkLoadingStatus: PropTypes.func.isRequired
};

export default Root;
<commit_msg>improve function<commit_after>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { Redirect } from 'react-router-dom';
import ScrollEvent from 'react-onscroll';

import Header from '../header/header';
import Footer from '../footer/footer';
import Map from '../map/map';
import WidgetTreeCover from '../widget-tree-cover/widget-tree-cover';
import WidgetTreeLocated from '../widget-tree-located/widget-tree-located';
import WidgetTreeLoss from '../widget-tree-loss/widget-tree-loss';
import WidgetTreeCoverLossAreas from '../widget-tree-cover-loss-areas/widget-tree-cover-loss-areas';
import WidgetAreasMostCoverGain from '../widget-areas-most-cover-gain/widget-areas-most-cover-gain';
import WidgetTotalAreaPlantations from '../widget-total-area-plantations/widget-total-area-plantations';
import WidgetTreeCoverGain from '../widget-tree-cover-gain/widget-tree-cover-gain';
import WidgetPlantationArea from '../widget-plantation-area/widget-plantation-area';
import WidgetStories from '../widget-stories/widget-stories';

class Root extends PureComponent {
  componentDidMount() {
    const { setInitialData } = this.props;
    setInitialData(this.props);
  }
  componentWillUpdate(nextProps) {
    const { iso, refreshCountryData, checkLoadingStatus } = this.props;
    if (iso !== nextProps.iso) {
      refreshCountryData(nextProps);
    } else {
      checkLoadingStatus(nextProps);
    }
  }

  goToTop() {
    if(!this.props.topPage) {
      window.scrollTo(0, 0);
      this.props.setPositionPage(true);
    }
  }

  handleScrollCallback() {
    // setPositionPage
   if (window.scrollY >= 59) {
     this.props.setPositionMap(true);
     this.props.setTopMap(0);
   }
   if(window.scrollY >= (document.getElementById('c-widget-stories').offsetTop - window.innerHeight)) {
     this.props.setPositionMap(false);
     this.props.setTopMap(document.getElementById('c-widget-stories').offsetTop - window.innerHeight);
   }
   if (window.scrollY < 59) {
     this.props.setPositionMap(false);
     this.props.setTopMap(59);
   }
 }

  render() {
    const { isLoading, topPage } = this.props;
    if (isLoading) {
      return (

Loading...

)
    } else {
      this.goToTop();
      return (

          <ScrollEvent handleScrollCallback={() => this.handleScrollCallback()} />

          <div className={`l-country__map ${this.props.fixed ? '-fixed' : ''}`} style={{top: this.props.topMap}}>
            <Map
              maxZoom={14}
              minZoom={3}
              mapOptions={{
                mapTypeId: 'grayscale',
                backgroundColor: '#99b3cc',
                disableDefaultUI: true,
                panControl: false,
                zoomControl: false,
                mapTypeControl: false,
                scaleControl: true,
                streetViewControl: false,
                overviewMapControl: false,
                tilt: 0,
                scrollwheel: false
              }} />

              <WidgetTreeCover />

              <WidgetTreeLocated />

              <WidgetTreeLoss />

              <WidgetTreeCoverLossAreas />

              <WidgetTreeCoverGain />

              <WidgetAreasMostCoverGain />

              <WidgetTotalAreaPlantations />

              <WidgetPlantationArea />

          <WidgetStories />

      )
    }
  }
}

Root.propTypes = {
  isLoading: PropTypes.bool.isRequired,
  iso: PropTypes.string.isRequired,
  countryRegion: PropTypes.number.isRequired,
  countryData: PropTypes.object.isRequired,
  countryRegions: PropTypes.array.isRequired,
  countriesList: PropTypes.array.isRequired,
  setInitialData: PropTypes.func.isRequired,
  refreshCountryData: PropTypes.func.isRequired,
  checkLoadingStatus: PropTypes.func.isRequired
};

export default Root;
<|endoftext|>