<repo_name>dsyne/whichMultiples<file_sep>/src/js/modules/Block.js
import { multipleLimit } from '../config/config'
import { createNode, toggleClass, hasClass, removeClass, addClass } from '../helpers/helpers'

const activeClass = 'active'
const highlightClass = 'highlight'

export default class Block {
    constructor(currentNumber, numbersArray) {
        this.state = {
            lastClicked: '',
            num: currentNumber,
            fullNumArray: numbersArray,
            filteredNumArray: '',
        }
    }

    setState(key, val) {
        return this.state = {
            ...this.state,
            [key]: val,
        }
    }

    resetClickState() {
        /* 
            When a item is clicked this.state.num changes
            for each multiple found, so reset to last
            clicked num
        */ 
        this.setState('num', this.state.lastClicked)
    }

    get(num) {
        this.setState('num', num)
        return document.querySelector(`[data-id="${this.state.num}"]`)
    }

    create(num) {
        this.setState('num', num)
        const item = createNode(
            `<li class="numbers__item" data-id="${this.state.num}">${this.state.num}</li>`
        )
        
        item.addEventListener('click', event => this.clickHandler(event.target.dataset.id))
        
        return item
    }

    clickHandler(event) {
        const item = this.get(this.state.num)
        this.setState('lastClicked', event)
      
        if(hasClass(item, activeClass)) {
            removeClass(item, activeClass)
            this.clearHighlights()
        } else {
            this.clearActive()
            addClass(item, activeClass)
            this.clearHighlights()
                .then(() => this.highlightMultiples())
        }
    }

    calculateMultiples(num) {
        return new Promise((resolve, reject) => {
            // Numbers over multipleLimit have no multiples
            if(num <= multipleLimit) {
                const filtered = this.state.fullNumArray.filter(item => item % num === 0)
                this.setState('filteredNumArray', filtered)

                resolve(filtered)
            } else {
                reject(new Error(`The number ${num} has no multiples! Please try a lower number`))
            }
        })
    }

    highlightMultiples() {
        this.calculateMultiples(this.state.num)
            .then(filtered => 
                filtered.forEach(item => {
                    this.setState('num', item)
                    this.toggleHighlight(this.state.num)
                    this.resetClickState()
                })
            )
            .catch(console.log)
    }
    
    toggleHighlight(num) {
        toggleClass(this.get(num), highlightClass)
    }   

    clearHighlights() {
        return new Promise((resolve, reject) => {
            const items = [...document.querySelectorAll(`.${highlightClass}`)]
            
            if(items) {
                items.forEach(item => this.toggleHighlight(item.dataset.id))
                this.resetClickState()
                resolve()
            }
            else {
                reject(new Error('No highlighted items'))
            }
        })

    }

    clearActive() {
        const items = [...document.querySelectorAll(`.${activeClass}`)]
        if(items) {
            items.forEach(item => removeClass(item, activeClass))
        }
    }
}<file_sep>/webpack.config.babel.js
import path from 'path'
import webpack from 'webpack'
import BrowserSyncPlugin from 'browser-sync-webpack-plugin'

export default {
  target: 'web',
  entry: './src/js/script.js',
  output: {
    path: path.join(process.cwd(), '/dist'),
    filename: 'script.js',
  },
  resolve: {
    extensions: ['.js', '.scss'],
  },
  plugins: [
    new BrowserSyncPlugin({
      server: './',
      files: [
        './src/**/*.{js,scss}',
      ],
      host: 'localhost',
      port: 7000,
      open: true,
      reloadOnRestart: true,
    }),
  ],
  module: {
    loaders: [
      {
        test: /\.js$/,
        loader: 'babel-loader',
      },
      {
        test: /\.scss$/,
        loader: ['style-loader', 'css-loader', 'sass-loader'],
      }
    ],
  },
}

<file_sep>/src/js/modules/Block.spec.js
import chai  from 'chai'
import spies  from 'chai-spies'
import { createNode, toggleClass, hasClass, removeClass, addClass } from '../helpers/helpers'
import Block from './Block'

const expect = chai.expect
chai.use(spies)

describe('Block Class', () => {

    const numbers = [1, 2, 3, 4, 5, 6]
    const block = new Block(1, numbers)

    describe('setState()', () => {
        it('Should update state with passed key value pair', () => {
            const testKey = 'aTest'
            block.setState(testKey, [])

            expect(block.state).to.include.keys(testKey)
            expect(block.state[testKey]).to.be.instanceof(Array)
        })    
    })

    describe('resetClickState()', () => {
        it('Should call setState() and set block.state.num to value of block.state.lastClicked', () => {
            block.resetClickState()
            expect(block.state.num).to.be.empty
        })    
    })

    describe('get()', () => {
        it('Should call setState to update block.state.num', () => {
            block.get(2)
            expect(block.state.num).to.equal(2)
        })    
    })

    describe('create()', () => {
        it('Should call setState to update block.state.num to passed value', () => {
            block.create(1)
            expect(block.state.num).to.equal(1)
        })

        it('Should call createNode()', () => {
            const spyCreate = chai.spy(createNode)
            block.create(2)
            expect(spyCreate).to.have.been.called
        })    

        it('Should return a DOM node', () => {
            const item = block.create(1)
            expect(item instanceof HTMLElement).to.be.true
        })    
    })
})
<file_sep>/README.md
# whichMultiples

### Install
`npm i`

### Start Server
`npm start`

### Tests
`npm test`

### Info

- Front end [http://localhost:7000](http://localhost:7000)
- Node version 7.7.2<file_sep>/src/js/modules/Game.js
import { blockCount } from '../config/config'
import { createNode } from '../helpers/helpers'
import Block from './Block'

export default class Game {
    constructor() {
        this.d = document
        this.blockCount = blockCount
        this.numbersArray = this.generateNumbersArray()
        this.container = this.d.createDocumentFragment()
    }

    init() {
        this.insertBlocks()
    }

    generateNumbersArray() {
        /*
            Creates an array of N length and fills with 
            range 0..N then shifts by one
        */
        return [...Array(this.blockCount)].map((v, i) => ++i)
    }    

    buildBlocks() {
        return new Promise((resolve, reject) => {
            if(this.numbersArray) {
                this.numbersArray.forEach(num => {
                    const item = new Block(num, this.numbersArray)   
                    this.container.appendChild(item.create(num))
                })
                resolve()
            } else {
                reject(new Error('Error with numbersArray'))
            }
        })
    }

    insertBlocks() {
        this.buildBlocks()
            .then(() =>
                this.d.querySelector('.numbers__list').appendChild(this.container)
            )
            .catch(console.log)
    }
}           