43 |
44 | {rows.map((row, index) => (
45 |
46 | )).toArray()}
47 |
48 | )
49 | }
50 | }
51 |
52 | export default Grid
--------------------------------------------------------------------------------
/src/GridState.js:
--------------------------------------------------------------------------------
1 | import {List, Map} from 'immutable'
2 | import {calcGrid, calcGridExcludeLastRow, calcVisibleGrid, insertItems} from './gridCalculations'
3 |
4 | class GridState {
5 |
6 | constructor({
7 | additionalHeight = 0,
8 | buffer = 0,
9 | containerWidth = 0,
10 | containerHeight = 0,
11 | minWidth = 0,
12 | more = false,
13 | offset = 0,
14 | offsetLeft = 0,
15 | padding = 0,
16 | grid = Map({
17 | rows: List(),
18 | height: 0
19 | })
20 | } = {}) {
21 |
22 | this.additionalHeight = additionalHeight
23 | this.buffer = buffer
24 | this.containerWidth = containerWidth
25 | this.containerHeight = containerHeight
26 | this.minWidth = minWidth
27 | this.more = more
28 | this.offset = offset
29 | this.offsetLeft = offsetLeft
30 | this.padding = padding
31 | this.grid = grid
32 | }
33 |
34 | getState() {
35 | const grid = this.more ? calcGridExcludeLastRow(this.grid) : this.grid
36 |
37 | const [ visibleGrid, lastVisibleRowIndex ] = calcVisibleGrid(grid, this.containerHeight, this.offset)
38 |
39 | const loadMoreAllowed = grid.get(`rows`).size - 1 - this.buffer <= lastVisibleRowIndex
40 |
41 | return {
42 | loadMoreAllowed,
43 | offset: visibleGrid.getIn([ `rows`, 0, `top` ]) || 0,
44 | padding: this.padding,
45 | ...visibleGrid.toObject()
46 | }
47 | }
48 |
49 | updateOffset(offset) {
50 | this.offset = offset
51 | }
52 |
53 | updateGrid(items, containerWidth, containerHeight, offset, more) {
54 | this.containerWidth = containerWidth
55 | this.containerHeight = containerHeight
56 | this.offset = offset
57 | this.more = more
58 |
59 | this.grid = calcGrid(items, this.additionalHeight, this.containerWidth, this.minWidth, this.offsetLeft, this.padding, this.padding)
60 | }
61 |
62 | insertItems(items, more) {
63 | this.more = more
64 |
65 | this.grid = insertItems(this.grid, items, this.additionalHeight, this.containerWidth, this.minWidth, this.offsetLeft, this.padding)
66 | }
67 | }
68 |
69 | export default GridState
--------------------------------------------------------------------------------
/src/Item.js:
--------------------------------------------------------------------------------
1 | import {List, Map} from 'immutable'
2 | import React, {Component, PropTypes} from 'react'
3 |
4 | const defaultItemStyle = {
5 | display: `inline-block`,
6 | position: `relative`,
7 | marginLeft: 0
8 | }
9 |
10 | const getDataPridicate = item => i => item.get(`id`) === i.get(`id`)
11 |
12 | class Item extends Component {
13 | render() {
14 | const {item = Map()} = this.props
15 | const {ItemComponent, additionalHeight, items, offsetLeft} = this.context
16 |
17 | const itemStyle = {
18 | ...defaultItemStyle,
19 | marginLeft: offsetLeft
20 | }
21 |
22 | const data = items.find(getDataPridicate(item))
23 |
24 | const props = {
25 | additionalHeight,
26 | data,
27 | height: item.get(`height`),
28 | width: item.get(`width`)
29 | }
30 |
31 | return (
32 |
33 |
34 |
35 | )
36 | }
37 | }
38 |
39 | Item.contextTypes = {
40 | ItemComponent: PropTypes.func,
41 | offsetLeft: PropTypes.number,
42 | items: PropTypes.instanceOf(List),
43 | additionalHeight: PropTypes.number
44 | }
45 |
46 | export default Item
--------------------------------------------------------------------------------
/src/Row.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from 'react'
2 |
3 | import Item from './Item'
4 |
5 | const defaultRowStyle = {
6 | position: `relative`,
7 | width: `100%`,
8 | marginLeft: 0,
9 | display: `flex`
10 | }
11 |
12 | class Row extends Component {
13 |
14 | render() {
15 | const {row} = this.props
16 | const {offsetLeft} = this.context
17 |
18 | const rowStyle = {
19 | ...defaultRowStyle,
20 | height: row.get(`height`),
21 | marginLeft: -offsetLeft
22 | }
23 |
24 | return (
25 |
26 | {row
27 | .get(`items`)
28 | .map((item, index) => (
29 | -
30 | ))
31 | .toArray()}
32 |
33 | )
34 | }
35 | }
36 |
37 | Row.contextTypes = {
38 | offsetLeft: PropTypes.number
39 | }
40 |
41 | export default Row
--------------------------------------------------------------------------------
/src/gridCalculations.js:
--------------------------------------------------------------------------------
1 | import {List, Map} from 'immutable'
2 |
3 | const scaleItemsToContainerWidth = (items, containerWidth, offsetLeft) => {
4 | const calculatedContainerWidth = containerWidth - (items.size - 1) * offsetLeft
5 | const sumWidth = items.reduce((acc, item) => acc + item.get(`width`), 0)
6 | const widthScaleFactor = calculatedContainerWidth / sumWidth
7 | return items.map(item => item.withMutations(i =>
8 | i.update(`height`, h => h * widthScaleFactor).update(`width`, w => w * widthScaleFactor)
9 | ))
10 | }
11 |
12 | const scaleItemsToMinWidth = (items, minWidth) => {
13 | const minWidthItem = items.sortBy(item => item.get(`width`)).first()
14 |
15 | if (minWidthItem.get(`width`) < minWidth) {
16 | const k = minWidth / minWidthItem.get(`width`)
17 |
18 | items = items.map(item =>
19 | item.withMutations(i =>
20 | i.update(`width`, w => w * k)
21 | .update(`height`, h => h * k)
22 | )
23 | )
24 | }
25 |
26 | return items
27 | }
28 |
29 | export const calcGridRow = (top, items, containerWidth, additionalHeight, minWidth, offsetLeft = 0, fullWidth = true) => {
30 | let extraItems = List()
31 | const minHeight = items.minBy(item => item.get(`height`)).get(`height`)
32 |
33 | let calculatedItems = items.map(item => item.withMutations(i => {
34 | const k = minHeight / item.get(`height`)
35 | const origWidth = i.get(`width`)
36 | const origHeight = i.get(`height`)
37 |
38 | return i
39 | .set(`origHeight`, origHeight)
40 | .set(`origWidth`, origWidth)
41 | .set(`height`, minHeight)
42 | .update(`width`, w => k * w)
43 | }))
44 |
45 | if (fullWidth) {
46 | calculatedItems = scaleItemsToContainerWidth(calculatedItems, containerWidth, offsetLeft)
47 |
48 | while (calculatedItems.some(i => i.get(`width`) < minWidth)) {
49 | extraItems = extraItems.unshift(items.last())
50 | items = items.pop()
51 |
52 | calculatedItems = calculatedItems.pop()
53 |
54 | calculatedItems = scaleItemsToContainerWidth(calculatedItems, containerWidth, offsetLeft)
55 | }
56 | } else {
57 | calculatedItems = scaleItemsToMinWidth(calculatedItems, minWidth)
58 | while (calculatedItems.reduce((acc, item) => acc + item.get(`width`), 0) > containerWidth) {
59 | extraItems = extraItems.unshift(items.last())
60 | items = items.pop()
61 |
62 | calculatedItems = calculatedItems.pop()
63 |
64 | calculatedItems = scaleItemsToMinWidth(calculatedItems, minWidth)
65 | }
66 | }
67 |
68 | return Map({
69 | row: Map({
70 | items: calculatedItems,
71 | top,
72 | height: calculatedItems.first().get(`height`) + additionalHeight
73 | }),
74 | extraItems
75 | })
76 | }
77 |
78 | export const calcGrid = (items, additionalHeight, containerWidth, minWidth, offsetLeft, padding = 0, initialTop = 0) => {
79 | const double = 2
80 | const actualContainerWidth = containerWidth - padding * double
81 |
82 | let width = 0
83 | let top = initialTop
84 | let itemsInRow = List()
85 | let rows = List()
86 |
87 | items.forEach(item => {
88 | width += item.get(`width`)
89 |
90 | if (width > actualContainerWidth && itemsInRow.size) {
91 | width = item.get(`width`)
92 |
93 | const calcGridRowResult = calcGridRow(top, itemsInRow, actualContainerWidth, additionalHeight, minWidth, offsetLeft)
94 |
95 | rows = rows.push(calcGridRowResult.get(`row`))
96 |
97 | top += calcGridRowResult.getIn([ `row`, `height` ])
98 |
99 | itemsInRow = calcGridRowResult.get(`extraItems`)
100 | }
101 |
102 | itemsInRow = itemsInRow.push(item)
103 | })
104 |
105 | while (itemsInRow.size) {
106 | const calcGridRowResult = calcGridRow(top, itemsInRow, actualContainerWidth, additionalHeight, minWidth, offsetLeft)
107 | rows = rows.push(calcGridRowResult.get(`row`))
108 |
109 | top += calcGridRowResult.getIn([ `row`, `height` ])
110 |
111 | itemsInRow = calcGridRowResult.get(`extraItems`)
112 | }
113 |
114 | if (rows.size) {
115 | rows = rows.update(rows.size - 1, row => {
116 | top -= row.get(`height`)
117 | const origItems = row.get(`items`)
118 | .map(item =>
119 | item.update(it =>
120 | it.withMutations(i =>
121 | i.set(`width`, i.get(`origWidth`))
122 | .set(`height`, i.get(`origHeight`))
123 | )
124 | )
125 | )
126 |
127 | const updatedRow = calcGridRow(top, origItems, actualContainerWidth, additionalHeight, minWidth, offsetLeft, false).get(`row`)
128 |
129 | top += updatedRow.get(`height`)
130 | return updatedRow
131 | })
132 | }
133 | return Map({
134 | rows,
135 | height: rows.reduce((acc, item) => acc + item.get(`height`), 0) + initialTop
136 | })
137 | }
138 |
139 | export const calcGridExcludeLastRow = (grid) => {
140 | return grid.get(`rows`).size ? grid.update(g => g
141 | .update(`height`, h => h - g.getIn([ `rows`, -1, `height` ]))
142 | .update(`rows`, r => r.skipLast(1))
143 | ) : grid
144 | }
145 |
146 | export const calcVisibleGrid = (grid, visibleAreaHeight, offset) => {
147 |
148 | let lastVisibleRowIndex = 0
149 | return [ grid.update(`rows`, r => {
150 | let acc = List()
151 | r.some((it, i) => {
152 | const top = it.get(`top`)
153 | const height = it.get(`height`)
154 |
155 | if (top >= offset || top + height > offset) {
156 | if (top >= offset + visibleAreaHeight) {
157 | return true
158 | }
159 |
160 | lastVisibleRowIndex = i
161 | acc = acc.push(it)
162 | }
163 |
164 | return false
165 | })
166 | return acc
167 | }), lastVisibleRowIndex ]
168 | }
169 |
170 | export const insertItems = (grid, items, additionalHeight, containerWidth, minWidth, offsetLeft, padding = 0) => {
171 | const lastRow = grid.getIn([ `rows`, -1 ])
172 |
173 | const itemsToCalc = lastRow
174 | .get(`items`)
175 | .map(i => i
176 | .update(`width`, () => i.get(`origWidth`))
177 | .update(`height`, () => i.get(`origHeight`)))
178 | .concat(items)
179 |
180 | return calcGrid(itemsToCalc, additionalHeight, containerWidth, minWidth, offsetLeft, padding, grid.get(`height`) - lastRow.get(`height`))
181 | .update(g =>
182 | g.update(`rows`,
183 | r => grid
184 | .get(`rows`)
185 | .skipLast(1)
186 | .concat(r)))
187 | }
--------------------------------------------------------------------------------
/src/gridStateFactory.js:
--------------------------------------------------------------------------------
1 | import GridState from './GridState'
2 |
3 | const gridStateFactory = (...args) => new GridState(...args)
4 |
5 | export default gridStateFactory
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export {default} from './AdaptiveGrid'
--------------------------------------------------------------------------------
/src/throttle.js:
--------------------------------------------------------------------------------
1 | const debounce = (func, wait) => {
2 | let timeout
3 | return (...args) => {
4 | const later = () => {
5 | timeout = null
6 | func(...args)
7 | }
8 | clearTimeout(timeout)
9 | timeout = setTimeout(later, wait)
10 | }
11 | }
12 |
13 | export default debounce
--------------------------------------------------------------------------------
/test/AdaptiveGrid.spec.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from 'react'
2 |
3 | import TestUtils from 'react-addons-test-utils'
4 |
5 | import expect from 'expect'
6 | import mockery from 'mockery'
7 | import rndoam from 'rndoam/lib/withImmutable'
8 |
9 | describe(`react-adaptive-grid`, () => {
10 |
11 | describe(`AdaptiveGrid`, () => {
12 | let AdaptiveGrid
13 |
14 | class DisplayMock extends Component {
15 | render() {
16 | return
17 | }
18 | }
19 |
20 | before(() => {
21 | mockery.enable({
22 | warnOnUnregistered: false
23 | })
24 |
25 | mockery.registerMock(`./Display`, DisplayMock);
26 | ({default: AdaptiveGrid} = require(`../src/AdaptiveGrid`))
27 | })
28 |
29 | after(() => {
30 | mockery.disable()
31 | mockery.deregisterAll()
32 | })
33 |
34 | it(`should transfer props into the context`, () => {
35 | DisplayMock.contextTypes = {
36 | ItemComponent: PropTypes.func,
37 | additionalHeight: PropTypes.number,
38 | items: PropTypes.object,
39 | offsetLeft: PropTypes.number
40 | }
41 |
42 | try {
43 | const props = {
44 | ItemComponent: rndoam.noop(),
45 | additionalHeight: rndoam.number(),
46 | items: rndoam.list(),
47 | offsetLeft: rndoam.number()
48 | }
49 |
50 | const tree = TestUtils.renderIntoDocument(
51 |
52 | )
53 |
54 | const display = TestUtils.findRenderedComponentWithType(tree, DisplayMock)
55 |
56 | expect(display.context).toEqual(props)
57 |
58 | } finally {
59 | DisplayMock.contextTypes = {}
60 | }
61 | })
62 |
63 | it(`should transfer props into the Display component`, () => {
64 | const props = {
65 | ItemComponent: rndoam.noop(),
66 | additionalHeight: rndoam.number(),
67 | buffer: rndoam.number(),
68 | minWidth: rndoam.number(),
69 | padding: rndoam.number(),
70 | offsetLeft: rndoam.number(),
71 | items: rndoam.list(),
72 | load: rndoam.noop(),
73 | loading: false,
74 | more: true
75 | }
76 |
77 | const tree = TestUtils.renderIntoDocument(
78 |
79 | )
80 |
81 | const display = TestUtils.findRenderedComponentWithType(tree, DisplayMock)
82 |
83 | const {ItemComponent, ...expectedProps} = props
84 |
85 | expect(display.props).toEqual(expectedProps)
86 | })
87 | })
88 | })
--------------------------------------------------------------------------------
/test/Display.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-magic-numbers */
2 |
3 | import React, {Component} from 'react'
4 |
5 | import ReactDOM from 'react-dom'
6 | import TestUtils from 'react-addons-test-utils'
7 |
8 | import expect from 'expect'
9 | import mockery from 'mockery'
10 | import rndoam from 'rndoam/lib/withImmutable'
11 |
12 | class GridMock extends Component {
13 | render() {
14 | return
15 | }
16 | }
17 |
18 | const gridStateMock = {}
19 |
20 | const gridStateFactoryMock = expect.createSpy()
21 | .andReturn(gridStateMock)
22 |
23 | const createSetDisplayClientBoundingRect = (Display) => (displayClientBoundingRectMock) => {
24 | const spy = expect.spyOn(Display.prototype, `getDisplayBoundingClientRect`)
25 | spy.andReturn(displayClientBoundingRectMock)
26 | return spy.restore
27 | }
28 |
29 | const createSetContentClientBoundingRect = (Display) => (contentClientBoundingRectMock) => {
30 | const spy = expect.spyOn(Display.prototype, `getContentBoundingClientRect`)
31 | spy.andReturn(contentClientBoundingRectMock)
32 | return spy.restore
33 | }
34 |
35 | const simulateScroll = (node) => {
36 | const event = document.createEvent(`Event`)
37 | event.initEvent(`scroll`, true, true)
38 | node.dispatchEvent(event)
39 | return event
40 | }
41 |
42 | const simulateWindowResize = () => {
43 | const event = document.createEvent(`Event`)
44 | event.initEvent(`resize`, true, true)
45 | window.dispatchEvent(event)
46 | return event
47 | }
48 |
49 | describe(`react-adaptive-grid`, () => {
50 |
51 | describe(`Display`, () => {
52 | let Display, setDisplayClientBoundingRect, setContentClientBoundingRect, mountNode
53 |
54 | before(() => {
55 | mockery.enable({
56 | warnOnUnregistered: false
57 | })
58 |
59 | mockery.registerMock(`./Grid`, GridMock)
60 | mockery.registerMock(`./gridStateFactory`, gridStateFactoryMock);
61 | ({default: Display} = require(`../src/Display`))
62 |
63 | setDisplayClientBoundingRect = createSetDisplayClientBoundingRect(Display)
64 | setContentClientBoundingRect = createSetContentClientBoundingRect(Display)
65 |
66 | })
67 |
68 | after(() => {
69 | mockery.disable()
70 | mockery.deregisterAll()
71 | })
72 |
73 | beforeEach(() => {
74 | mountNode = document.createElement(`div`)
75 | document.body.appendChild(mountNode)
76 | gridStateMock.getState = expect.createSpy()
77 | .andReturn({})
78 | gridStateMock.updateGrid = expect.createSpy()
79 | gridStateMock.updateOffset = expect.createSpy()
80 | gridStateMock.insertItems = expect.createSpy()
81 | })
82 |
83 | afterEach(() => {
84 | ReactDOM.unmountComponentAtNode(mountNode)
85 | expect.restoreSpies()
86 | })
87 |
88 | it(`should set initial state`, () => {
89 | const props = {
90 | minWidth: rndoam.number(),
91 | additionalHeight: rndoam.number(),
92 | buffer: rndoam.number(),
93 | offsetLeft: rndoam.number(),
94 | padding: rndoam.number(),
95 | more: true
96 | }
97 | const state = rndoam.object()
98 |
99 | gridStateMock.getState.andReturn(state)
100 |
101 | const display = new Display(props)
102 |
103 |
104 | expect(gridStateFactoryMock.calls[ 0 ].arguments[ 0 ])
105 | .toEqual(props)
106 |
107 | expect(display.state).toEqual(state)
108 | })
109 |
110 | it(`should update state when component did mount`, () => {
111 | const props = {
112 | items: rndoam.array(),
113 | more: true
114 | }
115 |
116 | const containerWidth = 500
117 | const containerHeight = 200
118 | const offset = 200
119 |
120 | const restoreDisplay = setDisplayClientBoundingRect({
121 | top: 0,
122 | width: containerWidth,
123 | height: containerHeight
124 | })
125 |
126 | const restoreContent = setContentClientBoundingRect({
127 | top: -1 * offset
128 | })
129 |
130 | const state = rndoam.object()
131 |
132 | gridStateMock
133 | .getState
134 | .andReturn(state)
135 |
136 | TestUtils
137 | .renderIntoDocument(
138 |
139 | )
140 |
141 | expect(gridStateMock.updateGrid.calls[ 0 ].arguments)
142 | .toEqual([
143 | props.items,
144 | containerWidth,
145 | containerHeight,
146 | offset,
147 | true
148 | ])
149 |
150 | expect(gridStateMock.getState.calls.length)
151 | .toEqual(2)
152 |
153 | restoreDisplay()
154 | restoreContent()
155 | })
156 |
157 | it(`should transfer properties into the Grid component`, () => {
158 |
159 | const state = {
160 | offset: rndoam.number(),
161 | height: rndoam.number(),
162 | rows: rndoam.array(),
163 | padding: rndoam.number()
164 | }
165 |
166 | const offsetLeft = rndoam.number()
167 |
168 | gridStateMock
169 | .getState
170 | .andReturn(state)
171 |
172 | const display = TestUtils.renderIntoDocument(
173 |
174 | )
175 |
176 | const grid = TestUtils.findRenderedComponentWithType(display, GridMock)
177 |
178 |
179 | expect(grid.props)
180 | .toEqual({
181 | ...state,
182 | offsetLeft
183 | })
184 | })
185 |
186 | it(`should update state on scroll`, () => {
187 |
188 | const restoreDisplay = setDisplayClientBoundingRect({
189 | top: 0,
190 | width: 100,
191 | height: 100
192 | })
193 |
194 | const offset = 100
195 |
196 | const display = TestUtils.renderIntoDocument(
197 |
198 | )
199 |
200 | const restoreContent = setContentClientBoundingRect({
201 | top: -1 * offset
202 | })
203 |
204 | const state = rndoam.object()
205 |
206 | gridStateMock
207 | .getState
208 | .andReturn(state)
209 |
210 | simulateScroll(display.display)
211 |
212 | expect(display.state)
213 | .toEqual(state)
214 |
215 | expect(gridStateMock.updateOffset.calls[ 0 ].arguments[ 0 ])
216 | .toEqual(offset)
217 |
218 | restoreDisplay()
219 | restoreContent()
220 | })
221 |
222 | it(`should update state on window resize`, (done) => {
223 |
224 | const items = rndoam.array()
225 |
226 | const display = TestUtils.renderIntoDocument(
227 |
228 | )
229 |
230 | const containerWidth = rndoam.number()
231 | const containerHeight = rndoam.number()
232 | const offset = rndoam.number()
233 |
234 | const restoreDisplay = setDisplayClientBoundingRect({
235 | top: 0,
236 | width: containerWidth,
237 | height: containerHeight
238 | })
239 |
240 | const restoreContent = setContentClientBoundingRect({
241 | top: -1 * offset
242 | })
243 | const state = rndoam.object()
244 |
245 | gridStateMock
246 | .getState
247 | .andReturn(state)
248 |
249 | simulateWindowResize()
250 |
251 | setTimeout(() => {
252 | expect(gridStateMock.updateGrid.calls[ 1 ].arguments)
253 | .toEqual([
254 | items,
255 | containerWidth,
256 | containerHeight,
257 | offset,
258 | true
259 | ])
260 |
261 | expect(display.state)
262 | .toEqual(state)
263 |
264 | restoreDisplay()
265 | restoreContent()
266 | done()
267 | }, 500)
268 | })
269 |
270 | it(`should remove listener from the window after component was unmount`, () => {
271 |
272 | const spyAddListener = expect
273 | .spyOn(window, `addEventListener`)
274 | .andCallThrough()
275 |
276 | ReactDOM.render(
277 | ,
278 | mountNode
279 | )
280 |
281 | expect(spyAddListener.calls.length).toEqual(1)
282 |
283 | const spyRemoveListener = expect
284 | .spyOn(window, `removeEventListener`)
285 | .andCallThrough()
286 |
287 | ReactDOM.unmountComponentAtNode(mountNode)
288 |
289 | expect(spyRemoveListener.calls.length).toEqual(1)
290 |
291 | expect(spyAddListener.calls[ 0 ].arguments[ 1 ]).toEqual(spyRemoveListener.calls[ 0 ].arguments[ 1 ])
292 | })
293 |
294 |
295 | it(`should not call the load method if it has already been called`, () => {
296 |
297 | const props = {
298 | items: rndoam.list(100),
299 | load: expect.createSpy(),
300 | loading: true,
301 | more: true
302 | }
303 |
304 | gridStateMock
305 | .getState
306 | .andReturn({
307 | shouldLoad: true
308 | })
309 |
310 | TestUtils.renderIntoDocument(
311 |
312 | )
313 |
314 | expect(props.load.calls.length).toEqual(0)
315 | })
316 |
317 | it(`should not call the load method if it's no more`, () => {
318 |
319 | const props = {
320 | items: rndoam.list(100),
321 | load: expect.createSpy(),
322 | loading: true,
323 | more: false
324 | }
325 |
326 | gridStateMock
327 | .getState
328 | .andReturn({
329 | shouldLoad: true
330 | })
331 |
332 | TestUtils.renderIntoDocument(
333 |
334 | )
335 |
336 | expect(props.load.calls.length).toEqual(0)
337 | })
338 |
339 | it(`should call the load`, () => {
340 |
341 | const props = {
342 | items: rndoam.list(100),
343 | load: expect.createSpy(),
344 | loading: false,
345 | more: true
346 | }
347 |
348 | gridStateMock
349 | .getState
350 | .andReturn({
351 | loadMoreAllowed: true
352 | })
353 |
354 | TestUtils.renderIntoDocument(
355 |
356 | )
357 |
358 | expect(props.load.calls.length).toEqual(1)
359 |
360 | })
361 |
362 | it(`should insert elements`, () => {
363 | const props = {
364 | items: rndoam.list(100),
365 | load: expect.createSpy(),
366 | loading: false,
367 | more: true
368 | }
369 |
370 | gridStateMock
371 | .getState
372 | .andReturn({
373 | shouldLoad: true
374 | })
375 |
376 | class Container extends Component {
377 | constructor() {
378 | super()
379 | this.state = props
380 | }
381 |
382 | componentDidMount() {
383 | this.setState({
384 | items: rndoam.list(200)
385 | })
386 | }
387 |
388 | render() {
389 | return
390 | }
391 | }
392 |
393 | TestUtils.renderIntoDocument(
394 |
395 | )
396 |
397 | expect(gridStateMock.insertItems.calls.length).toEqual(1)
398 |
399 | const {arguments: args} = gridStateMock.insertItems.calls[0]
400 |
401 | expect(args[0].size).toEqual(100)
402 | })
403 |
404 | })
405 | })
--------------------------------------------------------------------------------
/test/Grid.spec.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 |
3 | import TestUtils from 'react-addons-test-utils'
4 |
5 | import expect from 'expect'
6 | import mockery from 'mockery'
7 |
8 | class RowItem extends Component {
9 | render() {
10 | return
11 | }
12 | }
13 |
14 |
15 | describe(`react-adaptive-grid`, () => {
16 |
17 | describe(`Grid`, () => {
18 | let Grid
19 |
20 | before(() => {
21 | mockery.enable({
22 | warnOnUnregistered: false
23 | })
24 |
25 | mockery.registerMock(`./Row`, RowItem);
26 | ({default: Grid} = require(`../src/Grid`))
27 |
28 | })
29 |
30 | after(() => {
31 | mockery.disable()
32 | mockery.deregisterAll()
33 | })
34 |
35 | it(`should set height for scroll helper tag`, () => {
36 | const props = {
37 | offset: 100
38 | }
39 |
40 | const grid = TestUtils
41 | .renderIntoDocument(
42 |
43 | )
44 |
45 | const divs = TestUtils.scryRenderedDOMComponentsWithTag(grid, `div`)
46 |
47 | expect(divs[1].style.height).toEqual(`${props.offset}px`)
48 | })
49 |
50 |
51 | it(`should set height of the content area`, () => {
52 | const props = {
53 | height: 100
54 | }
55 |
56 | const grid = TestUtils
57 | .renderIntoDocument(
58 |
59 | )
60 |
61 | const divs = TestUtils.scryRenderedDOMComponentsWithTag(grid, `div`)
62 |
63 | expect(divs[0].style.height).toEqual(`${props.height}px`)
64 | })
65 |
66 | it(`should set left padding of the content area`, () => {
67 | const props = {
68 | padding: 100
69 | }
70 |
71 | const grid = TestUtils
72 | .renderIntoDocument(
73 |
74 | )
75 |
76 | const divs = TestUtils.scryRenderedDOMComponentsWithTag(grid, `div`)
77 |
78 | const {paddingLeft, paddingRight} = divs[0].style
79 |
80 | expect(paddingLeft).toEqual(`${props.padding}px`)
81 | expect(paddingRight).toEqual(`${props.padding}px`)
82 | })
83 | })
84 | })
--------------------------------------------------------------------------------
/test/GridState.spec.js:
--------------------------------------------------------------------------------
1 | import {List, Map} from 'immutable'
2 |
3 | import expect from 'expect'
4 | import expectImmutable from 'expect-immutable'
5 | import mockery from 'mockery'
6 | import rndoam from 'rndoam/lib/withImmutable'
7 |
8 | expect.extend(expectImmutable)
9 |
10 | describe(`react-adaptive-grid`, () => {
11 |
12 | describe(`GridState`, () => {
13 | let GridState
14 |
15 | const calcVisibleGridSpy = expect.createSpy()
16 | const calcGridExcludeLastRowSpy = expect.createSpy()
17 | const calcGridSpy = expect.createSpy()
18 | const insertItemsSpy = expect.createSpy()
19 |
20 | beforeEach(() => {
21 | mockery.enable({
22 | warnOnUnregistered: false
23 |
24 | })
25 | mockery.registerMock(`./gridCalculations`, {
26 | calcGrid: calcGridSpy,
27 | calcGridExcludeLastRow: calcGridExcludeLastRowSpy,
28 | calcVisibleGrid: calcVisibleGridSpy,
29 | insertItems: insertItemsSpy
30 | });
31 |
32 | ({default: GridState} = require(`../src/GridState`))
33 | })
34 |
35 | afterEach(() => {
36 | mockery.deregisterAll()
37 | mockery.disable()
38 |
39 | calcVisibleGridSpy.reset()
40 | calcGridSpy.reset()
41 | insertItemsSpy.reset()
42 | })
43 |
44 |
45 | it(`should initialize with default values`, () => {
46 | const gridState = new GridState()
47 |
48 | expect(gridState.additionalHeight).toEqual(0)
49 | expect(gridState.containerWidth).toEqual(0)
50 | expect(gridState.containerHeight).toEqual(0)
51 | expect(gridState.minWidth).toEqual(0)
52 | expect(gridState.more).toEqual(false)
53 | expect(gridState.offset).toEqual(0)
54 | expect(gridState.offsetLeft).toEqual(0)
55 | expect(gridState.padding).toEqual(0)
56 | expect(gridState.grid).toEqualImmutable(Map({
57 | rows: List(),
58 | height: 0
59 | }))
60 | })
61 |
62 | it(`should update offset`, () => {
63 | const gridState = new GridState()
64 | const offset = rndoam.number()
65 |
66 | gridState.updateOffset(offset)
67 |
68 | expect(gridState.offset).toEqual(offset)
69 | })
70 |
71 | it(`should calculate grid`, () => {
72 |
73 | const additionalHeight = rndoam.number()
74 | const items = rndoam.list()
75 | const containerWidth = rndoam.number()
76 | const containerHeight = rndoam.number()
77 | const offset = rndoam.number()
78 | const offsetLeft = rndoam.number()
79 | const minWidth = rndoam.number()
80 | const padding = rndoam.number()
81 |
82 | const gridState = new GridState({additionalHeight, minWidth, offsetLeft, padding})
83 |
84 | gridState.updateGrid(items, containerWidth, containerHeight, offset, true)
85 |
86 | expect(gridState.containerWidth).toEqual(containerWidth)
87 | expect(gridState.containerHeight).toEqual(containerHeight)
88 | expect(gridState.offset).toEqual(offset)
89 | expect(gridState.more).toEqual(true)
90 |
91 | expect(calcGridSpy.calls.length).toEqual(1)
92 |
93 | const {arguments: args} = calcGridSpy.calls[ 0 ]
94 |
95 | expect(args).toEqual([
96 | items, additionalHeight, containerWidth, minWidth, offsetLeft, padding, padding
97 | ])
98 | })
99 |
100 | it(`should get initial state`, () => {
101 | const padding = rndoam.number()
102 | const grid = Map({
103 | rows: List([
104 | Map(),
105 | Map()
106 | ])
107 | })
108 |
109 | const gridState = new GridState({grid, padding})
110 | const offset = rndoam.number()
111 | const height = rndoam.number()
112 | const rows = List([
113 | Map({
114 | top: offset
115 | })
116 | ])
117 |
118 | calcVisibleGridSpy.andReturn([ Map({
119 | rows,
120 | height
121 | }), 0 ])
122 |
123 | expect(gridState.getState())
124 | .toEqual({
125 | loadMoreAllowed: false,
126 | offset,
127 | rows,
128 | height,
129 | padding
130 | })
131 | expect(calcVisibleGridSpy.calls.length).toEqual(1)
132 | })
133 |
134 | it(`should pass into calcVisible function grid without last ro if loading awaiting`, () => {
135 | const grid = Map({
136 | rows: List([
137 | Map(),
138 | Map()
139 | ])
140 | })
141 |
142 | const gridState = new GridState({
143 | grid,
144 | more: true
145 | })
146 |
147 | const gridExcludeLastRow = Map({
148 | rows: List([
149 | Map()
150 | ])
151 | })
152 |
153 | calcGridExcludeLastRowSpy.andReturn(gridExcludeLastRow)
154 |
155 | gridState.getState()
156 |
157 | const {arguments: args} = calcVisibleGridSpy.calls[ 0 ]
158 |
159 | expect(args[ 0 ])
160 | .toBe(gridExcludeLastRow)
161 | })
162 |
163 | it(`should allow to load more items if last row is visible`, () => {
164 | const grid = Map({
165 | rows: List([
166 | Map(),
167 | Map()
168 | ])
169 | })
170 |
171 | const gridState = new GridState({
172 | grid
173 | })
174 |
175 | calcVisibleGridSpy.andReturn([ grid, 1 ])
176 |
177 | expect(gridState.getState().loadMoreAllowed).toBeTruthy()
178 | })
179 |
180 | it(`should allow to load more with buffer`, () => {
181 | const grid = Map({
182 | rows: List([
183 | Map(),
184 | Map(),
185 | Map(),
186 | Map()
187 | ])
188 | })
189 |
190 | const gridState = new GridState({
191 | grid,
192 | buffer: 3
193 | })
194 |
195 | calcVisibleGridSpy.andReturn([ grid, 1 ])
196 |
197 | expect(gridState.getState().loadMoreAllowed).toBeTruthy()
198 | })
199 |
200 | it(`should insert items and update more`, () => {
201 | const gridState = new GridState({
202 | more: true
203 | })
204 | const items = rndoam.list()
205 |
206 | gridState.insertItems(items, false)
207 |
208 | expect(gridState.more)
209 | .toEqual(false)
210 |
211 | expect(insertItemsSpy.calls.length).toEqual(1)
212 | })
213 | })
214 | })
--------------------------------------------------------------------------------
/test/Item.spec.js:
--------------------------------------------------------------------------------
1 | import {List, Map} from 'immutable'
2 | import React, {Component, PropTypes} from 'react'
3 |
4 | import Item from '../src/Item'
5 | import TestUtils from 'react-addons-test-utils'
6 |
7 | import contextify from 'react-contextify'
8 | import expect from 'expect'
9 | import rndoam from 'rndoam/lib/withImmutable'
10 |
11 | describe(`react-adaptive-grid`, () => {
12 | describe(`Item`, () => {
13 | class ItemComponentMock extends Component {
14 | render() {
15 | return
16 | }
17 | }
18 |
19 | const WithContext = contextify({
20 | ItemComponent: PropTypes.func,
21 | additionalHeight: PropTypes.number,
22 | items: PropTypes.object,
23 | offsetLeft: PropTypes.number
24 | }, ({ItemComponent, additionalHeight, items, offsetLeft}) => ({
25 | ItemComponent,
26 | additionalHeight,
27 | items,
28 | offsetLeft
29 | }))(Item)
30 |
31 | it(`should receive item props from the context`, () => {
32 |
33 | const props = {
34 | ItemComponent: ItemComponentMock,
35 | additionalHeight: rndoam.number(),
36 | items: List(),
37 | offsetLeft: rndoam.number()
38 | }
39 |
40 | const tree = TestUtils.renderIntoDocument(
41 |
42 | )
43 |
44 | const item = TestUtils.findRenderedComponentWithType(tree, Item)
45 |
46 | expect(item.context).toEqual(props)
47 | })
48 |
49 | it(`should transfer props into the ItemComponent`, () => {
50 | const width = rndoam.number()
51 | const height = rndoam.number()
52 |
53 | const itemFromGrid = Map({
54 | id: 2,
55 | width,
56 | height
57 | })
58 |
59 | const itemData = Map({
60 | id: 2,
61 | foo: `bar`
62 | })
63 |
64 | const items = List([
65 | Map({
66 | id: 1
67 | }),
68 | itemData,
69 | Map({
70 | id: 3
71 | })
72 | ])
73 |
74 | const props = {
75 | ItemComponent: ItemComponentMock,
76 | additionalHeight: rndoam.additionalHeight,
77 | item: itemFromGrid,
78 | items
79 | }
80 |
81 | const tree = TestUtils.renderIntoDocument(
82 |
83 | )
84 |
85 | const item = TestUtils.findRenderedComponentWithType(tree, ItemComponentMock)
86 |
87 | expect(item.props.data).toEqual(itemData)
88 | expect(item.props.additionalHeight).toEqual(props.additionalHeight)
89 | expect(item.props.height).toEqual(height)
90 | expect(item.props.width).toEqual(width)
91 | })
92 | })
93 | })
--------------------------------------------------------------------------------
/test/Row.spec.js:
--------------------------------------------------------------------------------
1 | import {List, Map} from 'immutable'
2 | import React, {Component, PropTypes} from 'react'
3 |
4 | import TestUtils from 'react-addons-test-utils'
5 |
6 | import contextify from 'react-contextify'
7 | import expect from 'expect'
8 | import mockery from 'mockery'
9 |
10 | class ItemMock extends Component {
11 | render() {
12 | return
13 | }
14 | }
15 |
16 |
17 | describe(`react-adaptive-grid`, () => {
18 |
19 | describe(`Row`, () => {
20 | let Row, WithContext
21 |
22 | before(() => {
23 | mockery.enable({
24 | warnOnUnregistered: false
25 | })
26 |
27 | mockery.registerMock(`./Item`, ItemMock);
28 | ({default: Row} = require(`../src/Row`))
29 |
30 | WithContext = contextify({
31 | offsetLeft: PropTypes.number
32 | }, ({offsetLeft}) => ({
33 | offsetLeft
34 | }))(Row)
35 | })
36 |
37 | after(() => {
38 | mockery.disable()
39 | mockery.deregisterAll()
40 | })
41 |
42 | it(`should render items`, () => {
43 | const item = Map()
44 | const props = {
45 | row: Map({
46 | items: List([
47 | item
48 | ])
49 | })
50 | }
51 |
52 | const grid = TestUtils
53 | .renderIntoDocument(
54 |
55 | )
56 |
57 | const items = TestUtils.scryRenderedComponentsWithType(grid, ItemMock)
58 |
59 | expect(items.length).toEqual(1)
60 | expect(items[ 0 ].props).toEqual({
61 | item
62 | })
63 | })
64 |
65 | it(`should set height`, () => {
66 | const height = 100
67 | const offsetLeft = 100
68 | const props = {
69 | row: Map({
70 | items: List(),
71 | height
72 | }),
73 | offsetLeft
74 | }
75 |
76 | const grid = TestUtils
77 | .renderIntoDocument(
78 |
79 | )
80 |
81 | const divs = TestUtils.scryRenderedDOMComponentsWithTag(grid, `div`)
82 | const {style} = divs[ 0 ]
83 |
84 | expect(style.height).toEqual(`${height}px`)
85 | expect(style.marginLeft).toEqual(`${-offsetLeft}px`)
86 | })
87 | })
88 | })
--------------------------------------------------------------------------------
/test/gridCalculations.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-magic-numbers */
2 |
3 | import {List, Map} from 'immutable'
4 |
5 | import {calcGrid, calcGridExcludeLastRow, calcGridRow, calcVisibleGrid, insertItems} from '../src/gridCalculations'
6 |
7 | import expect from 'expect'
8 | import expectImmutable from 'expect-immutable'
9 |
10 | expect.extend(expectImmutable)
11 |
12 | describe(`react-adaptive-grid`, () => {
13 |
14 | describe(`gridCalculations`, () => {
15 |
16 | describe(`calcGridRow->`, () => {
17 |
18 | it(`should return a list width exact width`, () => {
19 | const items = List([
20 | Map({
21 | height: 100,
22 | width: 250
23 | }),
24 | Map({
25 | height: 150,
26 | width: 300
27 | }),
28 | Map({
29 | height: 200,
30 | width: 200
31 | })
32 | ])
33 |
34 | const containerWidth = 1100
35 | const top = 100
36 | const additionalHeight = 130
37 | const minWidth = 200
38 |
39 | expect(calcGridRow(top, items, containerWidth, additionalHeight, minWidth))
40 | .toEqualImmutable(
41 | Map({
42 | extraItems: List(),
43 | row: Map({
44 | items: List([
45 | Map({
46 | origHeight: 100,
47 | origWidth: 250,
48 | height: 200,
49 | width: 500
50 | }),
51 | Map({
52 | origHeight: 150,
53 | origWidth: 300,
54 | height: 200,
55 | width: 400
56 | }),
57 | Map({
58 | origHeight: 200,
59 | origWidth: 200,
60 | height: 200,
61 | width: 200
62 | })
63 | ]),
64 | top,
65 | height: 330
66 | })
67 | })
68 | )
69 | })
70 |
71 | it(`should pop extra items`, () => {
72 | const items = List([
73 | Map({
74 | height: 100,
75 | width: 250
76 | }),
77 | Map({
78 | height: 150,
79 | width: 300
80 | }),
81 | Map({
82 | height: 200,
83 | width: 200
84 | }),
85 | Map({
86 | height: 200,
87 | width: 150
88 | })
89 | ])
90 |
91 | const containerWidth = 900
92 | const top = 100
93 | const additionalHeight = 130
94 | const minWidth = 300
95 |
96 | expect(calcGridRow(top, items, containerWidth, additionalHeight, minWidth))
97 | .toEqualImmutable(
98 | Map({
99 | extraItems: List([
100 | Map({
101 | height: 200,
102 | width: 200
103 | }),
104 | Map({
105 | height: 200,
106 | width: 150
107 | })
108 | ]),
109 | row: Map({
110 | items: List([
111 | Map({
112 | origHeight: 100,
113 | origWidth: 250,
114 | height: 200,
115 | width: 500
116 | }),
117 | Map({
118 | origHeight: 150,
119 | origWidth: 300,
120 | height: 200,
121 | width: 400
122 | })
123 | ]),
124 | top,
125 | height: 330
126 | })
127 | })
128 | )
129 | })
130 |
131 | it(`should calculate row taking in account offset left`, () => {
132 | const items = List([
133 | Map({
134 | height: 200,
135 | width: 250
136 | }),
137 | Map({
138 | height: 200,
139 | width: 300
140 | }),
141 | Map({
142 | height: 200,
143 | width: 200
144 | })
145 | ])
146 |
147 | const containerWidth = 850
148 | const top = 100
149 | const additionalHeight = 130
150 | const offsetLeft = 50
151 | const minWidth = 200
152 |
153 | expect(calcGridRow(top, items, containerWidth, additionalHeight, minWidth, offsetLeft))
154 | .toEqualImmutable(
155 | Map({
156 | extraItems: List(),
157 | row: Map({
158 | items: List([
159 | Map({
160 | origHeight: 200,
161 | origWidth: 250,
162 | height: 200,
163 | width: 250
164 | }),
165 | Map({
166 | origHeight: 200,
167 | origWidth: 300,
168 | height: 200,
169 | width: 300
170 | }),
171 | Map({
172 | origHeight: 200,
173 | origWidth: 200,
174 | height: 200,
175 | width: 200
176 | })
177 | ]),
178 | top,
179 | height: 330
180 | })
181 | })
182 | )
183 | })
184 |
185 | it(`should only lead items to the same height`, () => {
186 | const items = List([
187 | Map({
188 | height: 100,
189 | width: 250
190 | }),
191 | Map({
192 | height: 200,
193 | width: 200
194 | }),
195 | Map({
196 | height: 300,
197 | width: 300
198 | })
199 | ])
200 |
201 | const containerWidth = 900
202 | const top = 100
203 | const additionalHeight = 130
204 | const offsetLeft = 50
205 | const minWidth = 200
206 |
207 | expect(calcGridRow(top, items, containerWidth, additionalHeight, minWidth, offsetLeft, false))
208 | .toEqualImmutable(
209 | Map({
210 | extraItems: List(),
211 | row: Map({
212 | items: List([
213 | Map({
214 | origHeight: 100,
215 | origWidth: 250,
216 | height: 200,
217 | width: 500
218 | }),
219 | Map({
220 | origHeight: 200,
221 | origWidth: 200,
222 | height: 200,
223 | width: 200
224 | }),
225 | Map({
226 | origHeight: 300,
227 | origWidth: 300,
228 | height: 200,
229 | width: 200
230 | })
231 | ]),
232 | top,
233 | height: 330
234 | })
235 | })
236 | )
237 | })
238 |
239 | it(`should pop extra items while leading to min width`, () => {
240 | const items = List([
241 | Map({
242 | height: 100,
243 | width: 250
244 | }),
245 | Map({
246 | height: 200,
247 | width: 200
248 | }),
249 | Map({
250 | height: 300,
251 | width: 300
252 | })
253 | ])
254 |
255 | const containerWidth = 800
256 | const top = 100
257 | const additionalHeight = 130
258 | const offsetLeft = 50
259 | const minWidth = 200
260 |
261 | expect(calcGridRow(top, items, containerWidth, additionalHeight, minWidth, offsetLeft, false))
262 | .toEqualImmutable(
263 | Map({
264 | extraItems: List([
265 | Map({
266 | height: 300,
267 | width: 300
268 | })
269 | ]),
270 | row: Map({
271 | items: List([
272 | Map({
273 | origHeight: 100,
274 | origWidth: 250,
275 | height: 200,
276 | width: 500
277 | }),
278 | Map({
279 | origHeight: 200,
280 | origWidth: 200,
281 | height: 200,
282 | width: 200
283 | })
284 | ]),
285 | top,
286 | height: 330
287 | })
288 | })
289 | )
290 | })
291 | })
292 |
293 | describe(`calcGrid->`, () => {
294 | it(`should fit items into 1100px`, () => {
295 | const items = List([
296 | Map({
297 | height: 100,
298 | width: 250
299 | }),
300 | Map({
301 | height: 150,
302 | width: 300
303 | }),
304 | Map({
305 | height: 200,
306 | width: 200
307 | })
308 | ])
309 |
310 | const containerWidth = 1100
311 | const additionalHeight = 130
312 | const minWidth = 200
313 |
314 | expect(calcGrid(items, additionalHeight, containerWidth, minWidth))
315 | .toEqualImmutable(Map({
316 | rows: List([
317 | Map({
318 | items: List([
319 | Map({
320 | origHeight: 100,
321 | origWidth: 250,
322 | height: 200,
323 | width: 500
324 | }),
325 | Map({
326 | origHeight: 150,
327 | origWidth: 300,
328 | height: 200,
329 | width: 400
330 | }),
331 | Map({
332 | origHeight: 200,
333 | origWidth: 200,
334 | height: 200,
335 | width: 200
336 | })
337 | ]),
338 | top: 0,
339 | height: 330
340 | })
341 | ]),
342 | height: 330
343 | }))
344 | })
345 |
346 |
347 | it(`should fit two rows into 1100px with correct top values`, () => {
348 | const items = List([
349 | Map({
350 | height: 100,
351 | width: 250
352 | }),
353 | Map({
354 | height: 150,
355 | width: 300
356 | }),
357 | Map({
358 | height: 200,
359 | width: 200
360 | }),
361 | Map({
362 | height: 100,
363 | width: 250
364 | }),
365 | Map({
366 | height: 150,
367 | width: 300
368 | }),
369 | Map({
370 | height: 200,
371 | width: 200
372 | })
373 | ])
374 | const containerWidth = 1100
375 | const additionalHeight = 130
376 |
377 | expect(calcGrid(items, additionalHeight, containerWidth).size)
378 | .toEqual(2)
379 | })
380 |
381 | it(`should calculate right tops`, () => {
382 | const items = List([
383 | Map({
384 | height: 10,
385 | width: 10
386 | }),
387 | Map({
388 | height: 10,
389 | width: 10
390 | }),
391 | Map({
392 | height: 10,
393 | width: 10
394 | })
395 | ])
396 | const containerWidth = 10
397 | const additionalHeight = 130
398 | const minWidth = 10
399 | const grid = calcGrid(items, additionalHeight, containerWidth, minWidth)
400 |
401 | expect(grid.get(`rows`).reduce((acc, item) => [ ...acc, item.get(`top`) ], []))
402 | .toEqual([ 0, 140, 280 ])
403 | expect(grid.get(`height`))
404 | .toEqual(420)
405 | })
406 |
407 | it(`should fit items taking into account min. width limit`, () => {
408 | const items = List([
409 | Map({
410 | height: 200,
411 | width: 100
412 | }),
413 | Map({
414 | height: 200,
415 | width: 200
416 | }),
417 | Map({
418 | height: 200,
419 | width: 300
420 | })
421 | ])
422 |
423 | const containerWidth = 600
424 | const additionalHeight = 130
425 | const minWidth = 200
426 |
427 | expect(calcGrid(items, additionalHeight, containerWidth, minWidth))
428 | .toEqualImmutable(
429 | Map({
430 | rows: List([
431 | Map({
432 | items: List([
433 | Map({
434 | origHeight: 200,
435 | origWidth: 100,
436 | height: 400,
437 | width: 200
438 | }),
439 | Map({
440 | origHeight: 200,
441 | origWidth: 200,
442 | height: 400,
443 | width: 400
444 | })
445 | ]),
446 | top: 0,
447 | height: 530
448 | }),
449 | Map({
450 | items: List([
451 | Map({
452 | origHeight: 200,
453 | origWidth: 300,
454 | height: 200,
455 | width: 300
456 | })
457 | ]),
458 | top: 530,
459 | height: 330
460 | })
461 | ]),
462 | height: 860
463 | })
464 | )
465 | })
466 |
467 | it(`should calculate grid taking into account padding`, () => {
468 | const items = List([
469 | Map({
470 | height: 100,
471 | width: 100
472 | }),
473 | Map({
474 | height: 100,
475 | width: 100
476 | })
477 | ])
478 |
479 | const additionalHeight = 0
480 | const containerWidth = 300
481 | const padding = 100
482 | const minWidth = 100
483 | const offsetLeft = 0
484 |
485 | expect(calcGrid(items, additionalHeight, containerWidth, minWidth, offsetLeft, padding, padding))
486 | .toEqualImmutable(
487 | Map({
488 | rows: List([
489 | Map({
490 | items: List([
491 | Map({
492 | origHeight: 100,
493 | origWidth: 100,
494 | height: 100,
495 | width: 100
496 | })
497 | ]),
498 | top: 100,
499 | height: 100
500 | }),
501 | Map({
502 | items: List([
503 | Map({
504 | origHeight: 100,
505 | origWidth: 100,
506 | height: 100,
507 | width: 100
508 | })
509 | ]),
510 | top: 200,
511 | height: 100
512 | })
513 | ]),
514 | height: 300
515 | })
516 | )
517 | })
518 | })
519 |
520 | describe(`calcGridExcludeLastRow`, () => {
521 | it(`should return grid without last row`, () => {
522 | const grid = Map({
523 | rows: List([
524 | Map({
525 | items: List(),
526 | top: 0,
527 | height: 140
528 | }),
529 | Map({
530 | items: List(),
531 | top: 140,
532 | height: 140
533 | }),
534 | Map({
535 | items: List(),
536 | top: 280,
537 | height: 140
538 | })
539 | ]),
540 | height: 420
541 | })
542 |
543 | expect(calcGridExcludeLastRow(grid))
544 | .toEqualImmutable(Map({
545 | rows: List([
546 | Map({
547 | items: List(),
548 | top: 0,
549 | height: 140
550 | }),
551 | Map({
552 | items: List(),
553 | top: 140,
554 | height: 140
555 | })
556 | ]),
557 | height: 280
558 | }))
559 | })
560 |
561 | it(`should not touch grid if it hasn't rows`, () => {
562 | const grid = Map({
563 | rows: List(),
564 | height: 0
565 | })
566 |
567 | expect(calcGridExcludeLastRow(grid))
568 | .toBe(grid)
569 | })
570 | })
571 |
572 | describe(`calcVisibleGrid->`, () => {
573 | it(`should render only 1 row`, () => {
574 | const grid = Map({
575 | rows: List([
576 | Map({
577 | items: List(),
578 | top: 0,
579 | height: 140
580 | }),
581 | Map({
582 | items: List(),
583 | top: 140,
584 | height: 140
585 | }),
586 | Map({
587 | items: List(),
588 | top: 280,
589 | height: 140
590 | })
591 | ]),
592 | height: 420
593 | })
594 | const visibleAreaHeight = 140
595 | const offset = 140
596 |
597 | expect(calcVisibleGrid(grid, visibleAreaHeight, offset))
598 | .toEqual([ Map({
599 | rows: List([
600 | Map({
601 | items: List(),
602 | top: 140,
603 | height: 140
604 | })
605 | ]),
606 | height: 420
607 | }), 1 ])
608 | })
609 |
610 | it(`should render only 2 rows`, () => {
611 | const grid = Map({
612 | rows: List([
613 | Map({
614 | items: List(),
615 | top: 0,
616 | height: 140
617 | }),
618 | Map({
619 | items: List(),
620 | top: 140,
621 | height: 140
622 | }),
623 | Map({
624 | items: List(),
625 | top: 280,
626 | height: 140
627 | }),
628 | Map({
629 | items: List(),
630 | top: 420,
631 | height: 140
632 | })
633 | ]),
634 | height: 560
635 | })
636 | const visibleAreaHeight = 240
637 | const offset = 140
638 |
639 | expect(calcVisibleGrid(grid, visibleAreaHeight, offset))
640 | .toEqual([ Map({
641 | rows: List([
642 | Map({
643 | items: List(),
644 | top: 140,
645 | height: 140
646 | }),
647 | Map({
648 | items: List(),
649 | top: 280,
650 | height: 140
651 | })
652 | ]),
653 | height: 560
654 | }), 2 ])
655 | })
656 |
657 | it(`should render rows if partially visible`, () => {
658 | const grid = Map({
659 | rows: List([
660 | Map({
661 | items: List(),
662 | top: 0,
663 | height: 140
664 | }),
665 | Map({
666 | items: List(),
667 | top: 140,
668 | height: 140
669 | }),
670 | Map({
671 | items: List(),
672 | top: 280,
673 | height: 140
674 | }),
675 | Map({
676 | items: List(),
677 | top: 420,
678 | height: 140
679 | })
680 | ]),
681 | height: 560
682 | })
683 | const visibleAreaHeight = 240
684 | const offset = 130
685 |
686 | expect(calcVisibleGrid(grid, visibleAreaHeight, offset))
687 | .toEqual([ Map({
688 | rows: List([
689 | Map({
690 | items: List(),
691 | top: 0,
692 | height: 140
693 | }),
694 | Map({
695 | items: List(),
696 | top: 140,
697 | height: 140
698 | }),
699 | Map({
700 | items: List(),
701 | top: 280,
702 | height: 140
703 | })
704 | ]),
705 | height: 560
706 | }), 2 ])
707 | })
708 |
709 | })
710 |
711 | describe(`insertItems->`, () => {
712 | it(`should insert items`, () => {
713 | const initialGrid = Map({
714 | rows: List([
715 | Map({
716 | items: List(),
717 | top: 0,
718 | height: 140
719 | }),
720 | Map({
721 | items: List([
722 | Map({
723 | origWidth: 200,
724 | origHeight: 200
725 | })
726 | ]),
727 | top: 140,
728 | height: 140
729 | })
730 | ]),
731 | height: 280
732 | })
733 |
734 | const additionalHeight = 0
735 | const containerWidth = 400
736 |
737 | const items = List([
738 | Map({
739 | width: 200,
740 | height: 200
741 | }),
742 | Map({
743 | width: 200,
744 | height: 200
745 | })
746 | ])
747 |
748 | expect(insertItems(initialGrid, items, additionalHeight, containerWidth))
749 | .toEqualImmutable(Map({
750 | rows: List([
751 | Map({
752 | items: List(),
753 | top: 0,
754 | height: 140
755 | }),
756 | Map({
757 | items: List([
758 | Map({
759 | width: 200,
760 | height: 200,
761 | origWidth: 200,
762 | origHeight: 200
763 | }),
764 | Map({
765 | width: 200,
766 | height: 200,
767 | origWidth: 200,
768 | origHeight: 200
769 | })
770 | ]),
771 | top: 140,
772 | height: 200
773 | }),
774 | Map({
775 | items: List([
776 | Map({
777 | width: 200,
778 | height: 200,
779 | origWidth: 200,
780 | origHeight: 200
781 | })
782 | ]),
783 | top: 340,
784 | height: 200
785 | })
786 | ]),
787 | height: 540
788 | }))
789 | })
790 | })
791 | })
792 | })
793 |
--------------------------------------------------------------------------------
/test/setup.js:
--------------------------------------------------------------------------------
1 | import { jsdom } from 'jsdom'
2 |
3 | global.document = jsdom(``)
4 | global.window = document.defaultView
5 | global.navigator = global.window.navigator
--------------------------------------------------------------------------------
/webpack.config.examples.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | var path = require('path');
3 |
4 | var glob = require('glob');
5 | var webpack = require('webpack');
6 | var env = process.env.NODE_ENV;
7 |
8 | function createConfig (filepath) {
9 | var filename = path.basename(filepath, '.example.js');
10 | var outputPath = path.dirname(filepath);
11 |
12 | return {
13 |
14 | entry: path.resolve(__dirname, filepath),
15 |
16 | module: {
17 | loaders: [
18 | { test: /\.js$/, loaders: ['babel-loader'], exclude: /node_modules/ }
19 | ]
20 | },
21 |
22 | output: {
23 | filename: filename + '.js',
24 | path: path.resolve(__dirname, outputPath)
25 | },
26 |
27 | plugins: [
28 | {
29 | apply: function apply(compiler) {
30 | compiler.parser.plugin('expression global', function expressionGlobalPlugin() {
31 | this.state.module.addVariable('global', "(function() { return this; }()) || Function('return this')()")
32 | return false
33 | })
34 | }
35 | },
36 | new webpack.optimize.OccurenceOrderPlugin(),
37 | new webpack.DefinePlugin({
38 | 'process.env.NODE_ENV': JSON.stringify(env)
39 | })
40 | ]
41 | };
42 | }
43 |
44 | module.exports = glob.sync('examples/**/*.example.js').map(createConfig);
45 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var webpack = require('webpack');
4 | var env = process.env.NODE_ENV;
5 |
6 | var reactExternal = {
7 | root: 'React',
8 | commonjs2: 'react',
9 | commonjs: 'react',
10 | amd: 'react'
11 | };
12 |
13 | var config = {
14 | externals: {
15 | 'react': reactExternal
16 | },
17 | module: {
18 | loaders: [
19 | { test: /\.js$/, loaders: ['babel-loader'], exclude: /node_modules/ }
20 | ]
21 | },
22 | output: {
23 | library: 'ReactAdaptiveGrid',
24 | libraryTarget: 'umd'
25 | },
26 | plugins: [
27 | {
28 | apply: function apply(compiler) {
29 | compiler.parser.plugin('expression global', function expressionGlobalPlugin() {
30 | this.state.module.addVariable('global', "(function() { return this; }()) || Function('return this')()")
31 | return false
32 | })
33 | }
34 | },
35 | new webpack.optimize.OccurenceOrderPlugin(),
36 | new webpack.DefinePlugin({
37 | 'process.env.NODE_ENV': JSON.stringify(env)
38 | })
39 | ]
40 | };
41 |
42 | if (env === 'production') {
43 | config.plugins.push(
44 | new webpack.optimize.UglifyJsPlugin({
45 | compressor: {
46 | pure_getters: true,
47 | unsafe: true,
48 | unsafe_comps: true,
49 | screw_ie8: true,
50 | warnings: false
51 | }
52 | })
53 | )
54 | };
55 |
56 | module.exports = config;
--------------------------------------------------------------------------------