<commit_before>/**
 * TicTacToe
 *
 * @param scores {Object}
 * @param nextGame {Function}
 * @constructor
 */
var TicTacToe = function (scores, nextGame) {

    this.scores = scores;
    this.nextGame = nextGame;

    this.el = document.getElementById('ticTacToe');

    this.play = true;
    this.icon = true; // false = x; true = o
    this.squares = [];
    this.wins   = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8],

        [0, 3, 6],
        [1, 4, 7],
        [2, 5, 8],

        [6, 4, 2],
        [0, 4, 8]
    ];

    this.buildBoard()
        .setScores();
};

/**
 * getSquare
 *
 * @param index {Number}
 * @returns {Object}
 */
TicTacToe.prototype.getSquare = function (index) {

    var square = this.squares[index];

    if (square) {
        return square;
    }

    throw new Error('Square out of range: ' + index);
};

/**
 * getStatus
 * gets the status of a square and matches the icon in play
 *
 * @returns {Boolean}
 */
TicTacToe.prototype.getStatus = function (index) {

    var square = this.getSquare(index),
        icon = square.className === 'cross';

     return !!square.played && (icon === this.icon);
};

/**
 * getSquaresInPlay
 * gets the remaining squares in play that haven't been played
 *
 * @param counts {Boolean}
 * @returns {Number}
 */
TicTacToe.prototype.getSquaresInPlay = function (counts) {

    var squares = this.squares.filter(function (square) {
        return !square.played;
    });

    if (counts) {
        return squares.length;
    }

    return squares;
};

/**
 * getRandom
 * randomly selects an un-played square
 *
 * @TODO this method needs to use the AI class to work out where the user will play next
 *
 * @returns {string}
 */
TicTacToe.prototype.getRandom = function () {

    var squaresLeft = this.getSquaresInPlay(),
        rand = Math.round(Math.random() * (squaresLeft.length - 1));

    if (squaresLeft[rand]) {
        return squaresLeft[rand].getAttribute('data-index');
    }
};

/**
 * getCompleteText
 * gets the complete text to display at the end of a game
 *
 * @returns {string}
 */
TicTacToe.prototype.getCompleteText = function () {

    var icon = this.icon ? 'crosses' : 'noughts',
        text = 'Game over, ' + icon + ' win this time. \n',
        squaresLeft = this.getSquaresInPlay(true);

    if (!squaresLeft) {
        // @TODO still possible to have used up all the squares and won
        text = 'No winner this time. \n';
    }
    return text;
};

/**
 * buildBoard
 *
 * @returns TicTacToe {Object}
 */
TicTacToe.prototype.buildBoard = function () {

    var tictactoe = this,
        list = document.createElement('ul'),
        item,
        i;

    list.className = 'tictactoe';

    for (i = 0; i < 9; i += 1) {

        item = document.createElement('li');
        item.setAttribute('data-index', i);

        item.addEventListener('click', function (evt) {

            if (tictactoe.play) {
                tictactoe.playSquare.call(tictactoe, evt);
            }
        }, false);

        this.squares.push(item);

        list.appendChild(item);
    }

    this.el.innerHTML = '';
    this.el.appendChild(list);

    return this;
};

/**
 * playSquare
 * event handler for when a square is clicked
 *
 * @param evt {Event.Object} !optional
 * @param index {Number} !optional
 */
TicTacToe.prototype.playSquare = function (evt, index) {

    var square;

    index = evt ? evt.currentTarget.getAttribute('data-index') : index;

    square = this.getSquare(index);

    if (square.played) {
        return;
    }

    if (this.icon) {
        square.classList.add('cross');
        square.cross = true;
    } else {
        square.classList.add('nought');
        square.nought = true;
    }

    square.played = true;

    this.checkForWin(index);
};

/**
 * checkForWin
 *
 * @param index {Number|String}
 */
TicTacToe.prototype.checkForWin = function (index) {

    var accumulative,
        gameOver = false,
        squaresLeft = this.getSquaresInPlay(true);

    index = +index;

    // iterate the possibles to check for win
    this.wins.forEach(function (wins) {

        accumulative = true;

        if (wins.indexOf(index) < 0 || gameOver) {
            return;
        }

        wins.forEach(function (win) {
            if (!this.getStatus(win)) {
                accumulative = false;
            }
        }, this);

        if (accumulative || !squaresLeft) {
            gameOver = this.gameOver(accumulative);
        }
    }, this);

    // only swap to next player if game still in play
    if (this.play) {
        this.icon = !this.icon;

        if (!this.icon) {
            this.computerTurn();
        }
    }
};

/**
 * computerTurn
 * works out the computers next move and stops the user getting three in a row
 */
TicTacToe.prototype.computerTurn = function () {

    var play = this.getNextPlayable('nought');

    // if play not set get blocker
    if (!play) {
        play = this.getNextPlayable('cross');
    }

    // if play still not set - get random
    play = (play === null) ? this.getRandom() : play;

    // maybe put a small delay on the move, so as to emulate the computer is
    // thinking about the move
    this.playSquare(null, play);
};

/**
 * getNextPlayable
 * iterates the possible wins and returns a play square to play next
 *
 * @param className {String}
 * @returns {*}
 */
TicTacToe.prototype.getNextPlayable = function (className) {

    var squareToPlay = null,
        winningSquare,
        playedCount,
        marker,
        square;

    this.wins.forEach(function (wins) {

        winningSquare = 0;
        marker = null;
        playedCount = 0;

        wins.forEach(function (win) {

            square = this.getSquare(win);

            if (square.played) {
                playedCount += 1;
            }

            if (square.className === className) {
                winningSquare += 1;
            } else {
                marker = win;
            }
        }, this);

        if (winningSquare === 2 && playedCount < 3) {
            squareToPlay = marker || null;
        }
    }, this);

    return squareToPlay;
};

/**
 * gameOver
 * stops play
 *
 * @param win {Boolean} is there a win on the board
 */
TicTacToe.prototype.gameOver = function (win) {

    var score = document.createElement('div'),
        button = document.createElement('button');

    this.play = false;

    button.innerText = 'Play Again';

    score.className = 'message';
    score.innerText = this.getCompleteText();
    score.appendChild(button);

    this.el.appendChild(score);

    this.updateScores(win);

    button.addEventListener('click', this.nextGame, false);

    return this;
};

/**
 * updateScores
 * updates the dom with the winning score
 *
 * @param win {Boolean}
 */
TicTacToe.prototype.updateScores = function (win) {

    var winner = 'ties';

    if (win) {
        winner = this.icon ? 'you' : 'computer';
    }

    this.scores[winner] += 1;
    this.setScores();

    localStorage.setItem('scores', JSON.stringify(this.scores));
};

/**
 * setScores
 */
TicTacToe.prototype.setScores = function () {

    var prop;

    for (prop in this.scores) {
        if (this.scores.hasOwnProperty(prop)) {
            document.querySelector('.' + prop).innerHTML = this.scores[prop];
        }
    }
};

// loop for continual play
var saved = localStorage.getItem('scores'),
    defaults = {
        you: 0,
        ties: 0,
        computer: 0
    },
    scores = saved ? JSON.parse(saved) : defaults,
    newGame = function () {
        return new TicTacToe(scores, newGame);
    };

// start the game
newGame();
<commit_msg>Adding more logic to naming and tidy up<commit_after>/**
 * TicTacToe
 * constructor for the class
 *
 * @param scores {Object} persisted scores state, pulled from storage or defaults
 * @param nextGame {Function} callback method to start a new game
 *
 * @constructor
 */
var TicTacToe = function (scores, nextGame) {

    this.scores = scores;
    this.nextGame = nextGame;

    this.el = document.getElementById('ticTacToe');

    this.gameInPlay = true;
    this.symbol = true; // false = x; true = o
    this.squares = [];
    this.wins   = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8],

        [0, 3, 6],
        [1, 4, 7],
        [2, 5, 8],

        [6, 4, 2],
        [0, 4, 8]
    ];

    this.buildBoard()
        .setScores();
};

/**
 * getSquare
 * gets the square as indicated by the passed in index, throws out of range
 * error.
 *
 * @param index {Number} 0-8
 * @returns {Object}
 */
TicTacToe.prototype.getSquare = function (index) {

    var square = this.squares[index];

    if (square) {
        return square;
    }

    throw new Error('Square out of range: ' + index);
};

/**
 * getStatus
 * gets the status of a square being played and matching the symbol in play
 *
 * @returns {Boolean}
 */
TicTacToe.prototype.getStatus = function (index) {

    var square = this.getSquare(index),
        symbol = square.className === 'cross';

     return !!square.played && (symbol === this.symbol);
};

/**
 * getSquaresInPlay
 * gets the remaining squares in play that haven't been played, optional param
 * to only return the counts
 *
 * @param counts {Boolean} !optional
 * @returns {Number|Array} either the total counts or an array of squares
 */
TicTacToe.prototype.getSquaresInPlay = function (counts) {

    var squares = this.squares.filter(function (square) {
        return !square.played;
    });

    if (counts) {
        return squares.length;
    }

    return squares;
};

/**
 * getRandom
 * randomly selects an un-played square
 *
 * @TODO this method needs to use the AI class to work out where the user will play next o_O
 *
 * @returns {number}
 */
TicTacToe.prototype.getRandom = function () {

    var squaresLeft = this.getSquaresInPlay(),
        rand = Math.round(Math.random() * (squaresLeft.length - 1));

    if (squaresLeft[rand]) {
        return +squaresLeft[rand].getAttribute('data-index');
    }
};

/**
 * getCompleteText
 * gets the complete text to display at the end of a game
 *
 * @returns {string}
 */
TicTacToe.prototype.getCompleteText = function () {

    var symbol = this.symbol ? 'crosses' : 'noughts',
        text = 'Game over, ' + symbol + ' win this time. \n',
        squaresLeft = this.getSquaresInPlay(true);

    if (!squaresLeft) {
        // @TODO still possible to have used up all the squares and won
        text = 'No winner this time. \n';
    }
    return text;
};

/**
 * buildBoard
 * creates all the dom elements for the game to start and bind events to the
 * squares
 *
 * @returns TicTacToe {Object}
 */
TicTacToe.prototype.buildBoard = function () {

    var tictactoe = this,
        list = document.createElement('ul'),
        item,
        i;

    list.className = 'tictactoe';

    for (i = 0; i < 9; i += 1) {

        item = document.createElement('li');
        item.setAttribute('data-index', i);

        item.addEventListener('click', function (evt) {

            if (tictactoe.gameInPlay) {
                tictactoe.playSquare.call(tictactoe, evt);
            }
        }, false);

        this.squares.push(item);

        list.appendChild(item);
    }

    this.el.innerHTML = '';
    this.el.appendChild(list);

    return this;
};

/**
 * playSquare
 * event handler for when a square is clicked
 *
 * @param evt {Event.Object} !optional if sending through index
 * @param index {Number} !optional if event handler
 */
TicTacToe.prototype.playSquare = function (evt, index) {

    var square;

    index = evt ? evt.currentTarget.getAttribute('data-index') : index;

    square = this.getSquare(index);

    if (square.played) {
        return;
    }

    if (this.symbol) {
        square.classList.add('cross');
        square.cross = true;
    } else {
        square.classList.add('nought');
        square.nought = true;
    }

    square.played = true;

    this.checkForWin(index);
};

/**
 * checkForWin
 *
 * @param index {Number|String}
 */
TicTacToe.prototype.checkForWin = function (index) {

    var accumulative,
        gameOver = false,
        squaresLeft = this.getSquaresInPlay(true);

    index = +index;

    // iterate the possibles to check for win
    this.wins.forEach(function (wins) {

        accumulative = true;

        if (wins.indexOf(index) < 0 || gameOver) {
            return;
        }

        wins.forEach(function (win) {
            if (!this.getStatus(win)) {
                accumulative = false;
            }
        }, this);

        if (accumulative || !squaresLeft) {
            gameOver = this.gameOver(accumulative);
        }
    }, this);

    // only swap to next player if game still in play
    if (this.gameInPlay) {
        this.symbol = !this.symbol;

        if (!this.symbol) {
            this.computerTurn();
        }
    }
};

/**
 * computerTurn
 * works out the computers next move and stops the user getting three in a row
 */
TicTacToe.prototype.computerTurn = function () {

    // find possible nought win
    var nextMove = this.getNextPlayable('nought');

    // if no win possible check for user win and block
    if (!nextMove) {
        nextMove = this.getNextPlayable('cross');

        // if play still not set - select a random position
        if (!nextMove) {
            nextMove = this.getRandom();
        }
    }

    // play chosen square
    this.playSquare(null, nextMove);
};

/**
 * getNextPlayable
 * iterates the possible wins and returns a play square to play next
 *
 * @param className {String}
 * @returns {*}
 */
TicTacToe.prototype.getNextPlayable = function (className) {

    var squareToPlay = null,
        winningSquare,
        playedCount,
        marker,
        square;

    this.wins.forEach(function (wins) {

        winningSquare = 0;
        marker = null;
        playedCount = 0;

        wins.forEach(function (win) {

            square = this.getSquare(win);

            if (square.played) {
                playedCount += 1;
            }

            if (square.className === className) {
                winningSquare += 1;
            } else {
                marker = win;
            }
        }, this);

        if (winningSquare === 2 && playedCount < 3) {
            squareToPlay = marker || null;
        }
    }, this);

    return squareToPlay;
};

/**
 * gameOver
 * stops play
 *
 * @param win {Boolean} is there a win on the board
 */
TicTacToe.prototype.gameOver = function (win) {

    var score = document.createElement('div'),
        button = document.createElement('button');

    button.innerText = 'Play Again';
    button.addEventListener('click', this.nextGame, false);

    score.className = 'message';
    score.innerText = this.getCompleteText();
    score.appendChild(button);

    this.el.appendChild(score);

    this.updateScores(win);
    this.gameInPlay = false;

    return this;
};

/**
 * updateScores
 * updates the dom with the winning score
 *
 * @param win {Boolean}
 */
TicTacToe.prototype.updateScores = function (win) {

    var winner = 'ties';

    if (win) {
        winner = this.symbol ? 'you' : 'computer';
    }

    this.scores[winner] += 1;
    this.setScores();

    localStorage.setItem('scores', JSON.stringify(this.scores));
};

/**
 * setScores
 * sets the scores to the DOM
 */
TicTacToe.prototype.setScores = function () {

    var prop;

    for (prop in this.scores) {
        if (this.scores.hasOwnProperty(prop)) {
            document.querySelector('.' + prop).innerHTML = this.scores[prop];
        }
    }
};

// instantiate the class and start the game
var saved = localStorage.getItem('scores'),
    defaults = {
        you: 0,
        ties: 0,
        computer: 0
    },
    scores = saved ? JSON.parse(saved) : defaults,

    // loop for continual play
    newGame = function () {
        return new TicTacToe(scores, newGame);
    };

// start the game loop
newGame();
<|endoftext|>


<commit_before>import angular from 'angular';
import Immutable from 'immutable';
import squareImageModule from '../components/square-image';
import won from '../won-es6';
import { actionCreators }  from '../actions/actions';
import { attach } from '../utils';
import {
    labels,
    relativeTime,
} from '../won-label-utils';
import {
    selectOwnNeeds,
    selectLastUpdateTime,
    selectUnreadCountsByNeedAndType,
} from '../selectors';

import {
    seeksOrIs,
    inferLegacyNeedType,
} from '../won-utils';

import feedItemLineModule from './feed-item-line';

const serviceDependencies = ['$scope', '$interval', '$ngRedux'];
function genComponentConf() {
    let template = `

                <won-square-image
                    src="self.ownNeed.get('titleImg')"
                    title="self.ownNeedContent.get('dc:title')"
                    uri="self.ownNeed.get('@id')">
                </won-square-image>

                            {{ self.ownNeedContent.get('dc:title') }}

                            {{ self.createdOn }}

                            {{
                                self.labels.type[
                                    self.inferLegacyNeedType(self.ownNeed)
                                ]
                            }}

                <won-feed-item-line
                    class="fmil__item clickable"
                    ng-repeat="cnctUri in self.connectionUris track by $index"
                    connection-uri="cnctUri"
                    ng-show="$index < self.maxNrOfItemsShown"
                    ui-sref="post({
                        postUri: self.ownNeed.get('@id'),
                        connectionUri: cnctUri,
                        connectionType: cnct.getIn(['connection', 'hasConnectionState'])
                    })">
                </won-feed-item-line>

                        1 more activity

 self.maxNrOfItemsShown + 1"
                     ng-click="self.showMore()">
                        {{self.connectionUris.size - self.maxNrOfItemsShown}} more activities

                        {{ self.unreadMatchesCount() }} Matches

                        {{self.unreadRequestsCount()}} Incoming Requests

            `;

    class Controller {
        constructor() {
            attach(this, serviceDependencies, arguments);
            this.seeksOrIs = seeksOrIs;
            this.inferLegacyNeedType = inferLegacyNeedType;

            window.fi4dbg = this;

            this.labels = labels;

            const self = this;

            this.maxNrOfItemsShown = 3;
            const selectFromState = (state) => {
                const ownNeeds = selectOwnNeeds(state);
                const lastUpdated = selectLastUpdateTime(state);

                const unreadCountsByNeedAndType = selectUnreadCountsByNeedAndType(state);
                const ownNeed = ownNeeds && ownNeeds.get(self.needUri);
                const ownNeedContent = ownNeed && seeksOrIs(ownNeed);

                const cnctUriCollection = ownNeed.getIn(['won:hasConnections', 'rdfs:member']);
                const connectionUris = !cnctUriCollection?
                    [] : // if there's no cnctUriCollection, there's no connection uris
                    cnctUriCollection
                        .filter(c => c)
                        .map(c => c.get('@id'))
                        .toArray();

                return {
                    ownNeed,
                    ownNeedContent,
                    connectionUris,
                    createdOn: ownNeed && relativeTime(lastUpdated, ownNeed.get('dct:created')),
                    unreadCounts: unreadCountsByNeedAndType && unreadCountsByNeedAndType.get(self.needUri),
                }
            }
            const disconnect = this.$ngRedux.connect(selectFromState,actionCreators)(this);
            this.$scope.$on('$destroy', disconnect);
        }
        showMore() {
            this.maxNrOfItemsShown += 6;
        }
        unreadXCount(type) {
            return !this.unreadCounts?
                undefined : //ensure existence of count object
                this.unreadCounts.get(type)
        }
        unreadMatchesCount() {
            return this.unreadXCount(won.EVENT.HINT_RECEIVED)
        }
        unreadRequestsCount() {
            return this.unreadXCount(won.EVENT.CONNECT_RECEIVED)
        }
    }
    Controller.$inject = serviceDependencies;

    return {
        restrict: 'E',
        controller: Controller,
        controllerAs: 'self',
        bindToController: true, //scope-bindings -> ctrl
        scope: {
            needUri: '=',
        },
        template: template
    }
}

export default angular.module('won.owner.components.feedItem', [
    squareImageModule,
    feedItemLineModule,
])
    .directive('wonFeedItem', genComponentConf)
    .name;

<commit_msg>Fixed unread-counts in feed.<commit_after>import angular from 'angular';
import Immutable from 'immutable';
import squareImageModule from '../components/square-image';
import won from '../won-es6';
import { actionCreators }  from '../actions/actions';
import { attach } from '../utils';
import {
    labels,
    relativeTime,
} from '../won-label-utils';
import {
    selectOwnNeeds,
    selectLastUpdateTime,
    selectUnreadCountsByNeedAndType,
} from '../selectors';

import {
    seeksOrIs,
    inferLegacyNeedType,
} from '../won-utils';

import feedItemLineModule from './feed-item-line';

const serviceDependencies = ['$scope', '$interval', '$ngRedux'];
function genComponentConf() {
    let template = `

                <won-square-image
                    src="self.ownNeed.get('titleImg')"
                    title="self.ownNeedContent.get('dc:title')"
                    uri="self.ownNeed.get('@id')">
                </won-square-image>

                            {{ self.ownNeedContent.get('dc:title') }}

                            {{ self.createdOn }}

                            {{
                                self.labels.type[
                                    self.inferLegacyNeedType(self.ownNeed)
                                ]
                            }}

                <won-feed-item-line
                    class="fmil__item clickable"
                    ng-repeat="cnctUri in self.connectionUris track by $index"
                    connection-uri="cnctUri"
                    ng-show="$index < self.maxNrOfItemsShown"
                    ui-sref="post({
                        postUri: self.ownNeed.get('@id'),
                        connectionUri: cnctUri,
                        connectionType: cnct.getIn(['connection', 'hasConnectionState'])
                    })">
                </won-feed-item-line>

                        1 more activity

 self.maxNrOfItemsShown + 1"
                     ng-click="self.showMore()">
                        {{self.connectionUris.size - self.maxNrOfItemsShown}} more activities

                        {{ self.unreadMatchesCount() }} Matches

                        {{self.unreadRequestsCount()}} Incoming Requests

            `;

    class Controller {
        constructor() {
            attach(this, serviceDependencies, arguments);
            this.seeksOrIs = seeksOrIs;
            this.inferLegacyNeedType = inferLegacyNeedType;

            window.fi4dbg = this;

            this.labels = labels;

            const self = this;

            this.maxNrOfItemsShown = 3;
            const selectFromState = (state) => {
                const ownNeeds = selectOwnNeeds(state);
                const lastUpdated = selectLastUpdateTime(state);

                const unreadCountsByNeedAndType = selectUnreadCountsByNeedAndType(state);
                const ownNeed = ownNeeds && ownNeeds.get(self.needUri);
                const ownNeedContent = ownNeed && seeksOrIs(ownNeed);

                const cnctUriCollection = ownNeed.getIn(['won:hasConnections', 'rdfs:member']);
                const connectionUris = !cnctUriCollection?
                    [] : // if there's no cnctUriCollection, there's no connection uris
                    cnctUriCollection
                        .filter(c => c)
                        .map(c => c.get('@id'))
                        .toArray();

                return {
                    ownNeed,
                    ownNeedContent,
                    connectionUris,
                    createdOn: ownNeed && relativeTime(lastUpdated, ownNeed.get('dct:created')),
                    unreadCounts: unreadCountsByNeedAndType && unreadCountsByNeedAndType.get(self.needUri),
                }
            }
            const disconnect = this.$ngRedux.connect(selectFromState,actionCreators)(this);
            this.$scope.$on('$destroy', disconnect);
        }
        showMore() {
            this.maxNrOfItemsShown += 6;
        }
        unreadXCount(type) {
            return !this.unreadCounts?
                undefined : //ensure existence of count object
                this.unreadCounts.get(type)
        }
        unreadMatchesCount() {
            return this.unreadXCount(won.WONMSG.hintMessage);
        }
        unreadRequestsCount() {
            return this.unreadXCount(won.WONMSG.connectMessage);
        }
    }
    Controller.$inject = serviceDependencies;

    return {
        restrict: 'E',
        controller: Controller,
        controllerAs: 'self',
        bindToController: true, //scope-bindings -> ctrl
        scope: {
            needUri: '=',
        },
        template: template
    }
}

export default angular.module('won.owner.components.feedItem', [
    squareImageModule,
    feedItemLineModule,
])
    .directive('wonFeedItem', genComponentConf)
    .name;

<|endoftext|>


<commit_before>let fs = require('fs')

exports.keyValStore = function (dbInput) {
  if (dbInput) {
    let res
    while (delSpace(dbInput)) {
      res = dbOp(delSpace(dbInput))
      dbInput = res[1]
    }
    return res[0]
  }
  return null
}

let dbOp = factory(insertObj, deleteObj)

function factory (...parsers) {
  return function (In) {
    for (let i = 0; i < parsers.length; i++) {
      let result = parsers[i](In)
      if (result != null) return result
    }
    return null
  }
}

function insertObj (dbInput) {
  if (dbInput.slice(0, 6) === 'insert') {
    dbInput = delSpace(dbInput.slice(6))
    let dbObj = {}, dbArr = dbInput.split(' '), key = dbArr[0]
    dbInput = delSpace(dbInput.slice(key.length))
    dbArr = dbInput.split(' ')
    let value = dbArr[0]
    dbInput = delSpace(dbInput.slice(value.length))
    dbObj[key] = value
    let json = require('./db.json')
    json.push(dbObj)
    fs.writeFile('db.json', JSON.stringify(json, null, 4))
    return ['Data inserted', dbInput]
  }
  return null
}

function deleteObj (dbInput) {
  if (dbInput.slice(0, 6) === 'delete') {
    dbInput = delSpace(dbInput.slice(6))
    let dbArr = dbInput.split(' '), key = dbArr[0]
    console.log('key', key)
    dbInput = delSpace(dbInput.slice(key.length))
    let json = require('./db.json'), count = 0, flag = 0
    for (let ob of json) {
      if (ob === null) {
        count++
        continue
      }
      if (Object.keys(ob)[0] === key) {
        delete json[count]
        flag = 1
        break
      }
      count++
    }
    if (flag === 0) return null  // console.log(json)
    for (let i = 0; i < json.length; i++) {
      if (json[i] == null) json.splice(i, 1)
    }
    fs.writeFile('db.json', JSON.stringify(json, null, 4))
    return ['Data deleted', dbInput]
  }
  return null
}

function delSpace (dbInput) {
  while (/\s/.test(dbInput[0])) dbInput = dbInput.slice(1)
  return dbInput
}
<commit_msg>lists all key value pairs<commit_after>let fs = require('fs')

exports.keyValStore = function (dbInput) {
  if (dbInput) {
    let res
    while (delSpace(dbInput)) {
      res = dbOp(delSpace(dbInput))
      dbInput = res[1]
    }
    return res[0]
  }
  return null
}

let dbOp = factory(listAllObj, insertObj, deleteObj)

function factory (...parsers) {
  return function (In) {
    for (let i = 0; i < parsers.length; i++) {
      let result = parsers[i](In)
      if (result != null) return result
    }
    return null
  }
}

function listAllObj (dbInput) {
  if (dbInput.split(' ')[0] === 'listAll') {
    dbInput = delSpace(dbInput.slice(7))
    let json = require('./db.json')
    // let _ = require('lodash')
    for (let ob of json) console.log(Object.keys(ob)[0], ':', ob[Object.keys(ob)[0]])
    return ['********', dbInput]
  }
  return null
}

function insertObj (dbInput) {
  if (dbInput.slice(0, 6) === 'insert') {
    dbInput = delSpace(dbInput.slice(6))
    let dbObj = {}, dbArr = dbInput.split(' '), key = dbArr[0]
    dbInput = delSpace(dbInput.slice(key.length))
    dbArr = dbInput.split(' ')
    let value = dbArr[0]
    dbInput = delSpace(dbInput.slice(value.length))
    dbObj[key] = value
    let json = require('./db.json')
    json.push(dbObj)
    fs.writeFile('db.json', JSON.stringify(json, null, 4))
    return ['Data inserted', dbInput]
  }
  return null
}

function deleteObj (dbInput) {
  if (dbInput.slice(0, 6) === 'delete') {
    dbInput = delSpace(dbInput.slice(6))
    let dbArr = dbInput.split(' '), key = dbArr[0]
    console.log('key', key)
    dbInput = delSpace(dbInput.slice(key.length))
    let json = require('./db.json'), count = 0, flag = 0
    for (let ob of json) {
      if (ob === null) {
        count++
        continue
      }
      if (Object.keys(ob)[0] === key) {
        delete json[count]
        flag = 1
        break
      }
      count++
    }
    if (flag === 0) return null  // console.log(json)
    for (let i = 0; i < json.length; i++) {
      if (json[i] == null) json.splice(i, 1)
    }
    fs.writeFile('db.json', JSON.stringify(json, null, 4))
    return ['Data deleted', dbInput]
  }
  return null
}

function delSpace (dbInput) {
  while (/\s/.test(dbInput[0])) dbInput = dbInput.slice(1)
  return dbInput
}
<|endoftext|>


<commit_before>/**
 * binary.operations.js
 * Management over available PHP versions
 */

/* Count of associated php binaries */
function binaryGetCount() {
  return Object.keys(conf.get('php.versions')).length;
}

/* Converts binary string to a save-safe string */
function binaryConvertVersionToSave(version) {
  return version.replace(/\./g, ':');
}

/* Converts binary save-safe string to regular string */
function binaryConvertVersionToShow(version) {
  return version.replace(/:/g, '.');
}

/* Gets the path of bundled PHP binary */
function getBundledPhpPath() {
  switch (conf.get('system.os')) {
    case 'osx':
      return Path.join(__dirname, 'php', 'osx');
    case 'win':
      return Path.join(__dirname, 'php', 'php-cgi.exe');
  }
}
/**
* Gets version of a binary
* @param {string} path - path to php binary
* @param {string} replaced - replacing . with : (configstore workaround)
*/
function binaryGetVersion(path, replaced) {
  let response;
  try {
    response = runner.execSync('"' + path + '" --version', { encoding: 'utf8' });
  } catch (e) {
    /* eslint-disable no-console */
    console.log(e);
    /* eslint-enable no-console */
  }

  // Is this PHP?
  if (/^PHP/.test(response)) {
    // Get PHP version
    const result = response.match(/^PHP ([0-9\.]+)/);
    if (result && result[1]) {
      if (replaced) {
        return binaryConvertVersionToSave(result[1]);
      }

      return result[1];
    }
  }

  return false;
}

/**
 * Generate an HTML template of a listing line
 */
function binaryLineGetTemplate(version, path, inUse) {
  return [
    '
',
    '  ' + binaryConvertVersionToShow(version) + '	',
    '  ' + path + '	',
    '  ',
    '
',
    '      <button class="btn btn-default btn-xs" onclick="makeDefaultVersion(\''
            + version + '\')">',
    '        ',
    '      </button>',
    '      <button class="btn btn-default btn-xs" onclick="removeVersion(\'' + version + '\')">',
    '        ',
    '      </button>',
    '
',
    '  	',
    '
'
  ].join('\n');
}

/**
 * Binary list functions
 */
function binaryUpdateList() {
  $('#binary-list').empty();

  const versions = conf.get('php.versions');
  const inUse = conf.get('php.default');

  // Adds bundled version manually
  versions['bundled'] = 'Integrated version';

  // Rewrites PHP versions list
  Object.keys(versions).forEach((v) => {
    $('#binary-list').append(binaryLineGetTemplate(v, versions[v], (inUse === v)));
  });
}

/**
 * Get current version
 */
function phpGetCurrVersion() {
  const curr = conf.get('php.default');

  if (curr) {
    return binaryConvertVersionToShow(conf.get('php.default'));
  }

  return false;
}

/* Updates binary path used by the runner */
function updatePhpPath() {
  // Are we using bundled version?
  if (conf.get('php.default') === 'bundled') {
    phpPath = getBundledPhpPath();
  } else {
    phpPath = conf.get('php.versions.' + conf.get('php.default'));
  }

  // Change PHP version number shown in app
  $('#run-version').html(phpGetCurrVersion());
}

/**
 * Set new PHP binary as default
 * @param {string} which - version / if given none, auto-select
 */
function binarySetNewDefault(which) {
  if (which) {
    conf.set('php.default', which);
  } else if (binaryGetCount()) {
    // Set first option on the list as default (if we have any)
    const vKeys = Object.keys(conf.get('php.versions'));
    conf.set('php.default', vKeys[0]);
  } else {
    conf.del('php.default');
  }
  updatePhpPath();
}

/* Adds a binary, if valid */
function binaryAdd(path) {
  // Is this a file?
  try {
    const file = fs.lstatSync(path);
    if (!file.isFile() && !file.isSymbolicLink()) {
      return false;
    }
  } catch (e) {
    /* eslint-disable no-console */
    console.log(e);
    /* eslint-enable no-console */

    // I couldn't even find the file!!!
    return false;
  }

  // So, let's find out it's version
  const vers = binaryGetVersion(path);

  // Is this a valid version?
  if (!vers) {
    return false;
  }

  // Saves to the versions list
  conf.set('php.versions.' + binaryConvertVersionToSave(vers), path);
  return true;
}

/* Searches for a PHP binary */
function binaryDialogAdd() {
  const file = dialog.showOpenDialog({
    title: i18n.__('Find PHP binary')
  });

  if (file && file[0]) {
    // Get path
    const path = file[0];

    // Oops! Invalid PHP binary!
    if (!binaryAdd(path)) {
      dialog.showErrorBox(i18n.__('Error'), i18n.__('Invalid PHP binary'));
      return;
    }

    // Is this our first?
    if (binaryGetCount() === 1) {
      // Please, set it as our new default
      binarySetNewDefault();
    }

    // Update list
    binaryUpdateList();
  }
}

/* Remove a PHP version from listing */
function binaryRemove(version) {
  conf.del('php.versions.' + version);
  // Are you deleting your default version?
  if (conf.get('php.default') === version) {
    binarySetNewDefault();
  }
}

/**
 * Set default PHP version to run code
 * @param {string} version - version to set as default
 */
function makeDefaultVersion(version) {
  binarySetNewDefault(version);
  binaryUpdateList();
}

/**
 * Remove a PHP version from the listing
 * @param {string} version - version to remove
 */
function removeVersion(version) {
  const opt = dialog.showMessageBox({
    type: 'question',
    title: i18n.__('Are you sure?'),
    message: i18n.__('Removing {{version}} version. Are you sure?',
                    { version: binaryConvertVersionToShow(version) }),
    buttons: [i18n.__('Yes'), i18n.__('No')],
  });

  // Yes = 0; No = 1
  if (opt === 0) {
    binaryRemove(version);
    binaryUpdateList();
  }
}
<commit_msg>Fixing PHP binaries listing bug<commit_after>/**
 * binary.operations.js
 * Management over available PHP versions
 */

/* Count of associated php binaries */
function binaryGetCount() {
  return Object.keys(conf.get('php.versions')).length;
}

/* Converts binary string to a save-safe string */
function binaryConvertVersionToSave(version) {
  return version.replace(/\./g, ':');
}

/* Converts binary save-safe string to regular string */
function binaryConvertVersionToShow(version) {
  return version.replace(/:/g, '.');
}

/* Gets the path of bundled PHP binary */
function getBundledPhpPath() {
  switch (conf.get('system.os')) {
    case 'osx':
      return Path.join(__dirname, 'php', 'osx');
    case 'win':
      return Path.join(__dirname, 'php', 'php-cgi.exe');
  }
}
/**
* Gets version of a binary
* @param {string} path - path to php binary
* @param {string} replaced - replacing . with : (configstore workaround)
*/
function binaryGetVersion(path, replaced) {
  let response;
  try {
    response = runner.execSync('"' + path + '" --version', { encoding: 'utf8' });
  } catch (e) {
    /* eslint-disable no-console */
    console.log(e);
    /* eslint-enable no-console */
  }

  // Is this PHP?
  if (/^PHP/.test(response)) {
    // Get PHP version
    const result = response.match(/^PHP ([0-9\.]+)/);
    if (result && result[1]) {
      if (replaced) {
        return binaryConvertVersionToSave(result[1]);
      }

      return result[1];
    }
  }

  return false;
}

/**
 * Generate an HTML template of a listing line
 */
function binaryLineGetTemplate(version, path, inUse) {
  return [
    '
',
    '  ' + binaryConvertVersionToShow(version) + '	',
    '  ' + path + '	',
    '  ',
    '
',
    '      <button class="btn btn-default btn-xs" onclick="makeDefaultVersion(\''
            + version + '\')">',
    '        ',
    '      </button>',
    '      <button class="btn btn-default btn-xs" onclick="removeVersion(\'' + version + '\')">',
    '        ',
    '      </button>',
    '
',
    '  	',
    '
'
  ].join('\n');
}

/**
 * Binary list functions
 */
function binaryUpdateList() {
  $('#binary-list').empty();

  let versions = conf.get('php.versions');
  const inUse = conf.get('php.default');

  // Adds bundled version manually
  if (!versions) versions = [];
  versions['bundled'] = 'Integrated version';

  // Rewrites PHP versions list
  Object.keys(versions).forEach((v) => {
    $('#binary-list').append(binaryLineGetTemplate(v, versions[v], (inUse === v)));
  });
}

/**
 * Get current version
 */
function phpGetCurrVersion() {
  const curr = conf.get('php.default');

  if (curr) {
    return binaryConvertVersionToShow(conf.get('php.default'));
  }

  return false;
}

/* Updates binary path used by the runner */
function updatePhpPath() {
  // Are we using bundled version?
  if (conf.get('php.default') === 'bundled') {
    phpPath = getBundledPhpPath();
  } else {
    phpPath = conf.get('php.versions.' + conf.get('php.default'));
  }

  // Change PHP version number shown in app
  $('#run-version').html(phpGetCurrVersion());
}

/**
 * Set new PHP binary as default
 * @param {string} which - version / if given none, auto-select
 */
function binarySetNewDefault(which) {
  if (which) {
    conf.set('php.default', which);
  } else if (binaryGetCount()) {
    // Set first option on the list as default (if we have any)
    const vKeys = Object.keys(conf.get('php.versions'));
    conf.set('php.default', vKeys[0]);
  } else {
    conf.del('php.default');
  }
  updatePhpPath();
}

/* Adds a binary, if valid */
function binaryAdd(path) {
  // Is this a file?
  try {
    const file = fs.lstatSync(path);
    if (!file.isFile() && !file.isSymbolicLink()) {
      return false;
    }
  } catch (e) {
    /* eslint-disable no-console */
    console.log(e);
    /* eslint-enable no-console */

    // I couldn't even find the file!!!
    return false;
  }

  // So, let's find out it's version
  const vers = binaryGetVersion(path);

  // Is this a valid version?
  if (!vers) {
    return false;
  }

  // Saves to the versions list
  conf.set('php.versions.' + binaryConvertVersionToSave(vers), path);
  return true;
}

/* Searches for a PHP binary */
function binaryDialogAdd() {
  const file = dialog.showOpenDialog({
    title: i18n.__('Find PHP binary')
  });

  if (file && file[0]) {
    // Get path
    const path = file[0];

    // Oops! Invalid PHP binary!
    if (!binaryAdd(path)) {
      dialog.showErrorBox(i18n.__('Error'), i18n.__('Invalid PHP binary'));
      return;
    }

    // Is this our first?
    if (binaryGetCount() === 1) {
      // Please, set it as our new default
      binarySetNewDefault();
    }

    // Update list
    binaryUpdateList();
  }
}

/* Remove a PHP version from listing */
function binaryRemove(version) {
  conf.del('php.versions.' + version);
  // Are you deleting your default version?
  if (conf.get('php.default') === version) {
    binarySetNewDefault();
  }
}

/**
 * Set default PHP version to run code
 * @param {string} version - version to set as default
 */
function makeDefaultVersion(version) {
  binarySetNewDefault(version);
  binaryUpdateList();
}

/**
 * Remove a PHP version from the listing
 * @param {string} version - version to remove
 */
function removeVersion(version) {
  const opt = dialog.showMessageBox({
    type: 'question',
    title: i18n.__('Are you sure?'),
    message: i18n.__('Removing {{version}} version. Are you sure?',
                    { version: binaryConvertVersionToShow(version) }),
    buttons: [i18n.__('Yes'), i18n.__('No')],
  });

  // Yes = 0; No = 1
  if (opt === 0) {
    binaryRemove(version);
    binaryUpdateList();
  }
}
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import {
  StatusBar,
  View,
  StyleSheet,
  LayoutAnimation,
  Platform,
  TextInput,
  ActivityIndicator,
} from 'react-native';
import {
  NavigationStyles,
} from '@exponent/ex-navigation';
import colors from '../config/colors';
import KeyboardEventListener from 'KeyboardEventListener';
import Swiper from 'react-native-swiper';
import Icon from '../components/CrossPlatformIcon';
import GetEventName from '../components/GetEventName';
import GetEventType from '../components/GetEventType';
import GetEventDate from '../components/GetEventDate';
import GetEventTime from '../components/GetEventTime';
import GetEventUrl from '../components/GetEventUrl';
import GetEventDescription from '../components/GetEventDescription';
import GetEventLocation from '../components/GetEventLocation';
import CancelButton from '../components/NavBarCancelButton';
import moment from 'moment';
import filter from '../utils/filter';
import connectDropdownAlert from '../utils/connectDropdownAlert';
import { isExponentPushToken, sendPushNotificationAsync } from '../utils/ExponentPushClient';
import _ from 'lodash';
/**
 *  Allows user to create new events for school
 */
@connectDropdownAlert
export default class NewEventScreen extends Component {
  static route = {
    navigationBar: {
      visible: true,
      title: 'NEW EVENT',
      tintColor: colors.black,
      renderLeft: () => null,
      renderRight: () => <CancelButton />,
      borderBottomColor: 'transparent',
      titleStyle: {
        fontFamily: 'open-sans-bold',
      },
      backgroundColor: 'white',
    },
    styles: {
      ...NavigationStyles.SlideHorizontal,
    },
  }

  static propTypes = {
    navigator: PropTypes.object.isRequired,
    refresh: PropTypes.func,
    alertWithType: PropTypes.func.isRequired,
  }

  state = {
    name: null,
    date: moment.utc().toDate(),
    time: moment.utc().toDate().getTime(),
    url: '',
    submitting: false,
    type: null,
    description: null,
    location: null,
    keyboardHeight: 0,
  }

  componentWillMount() {
    this._unsubscribe = KeyboardEventListener.subscribe(this._onKeyboardVisibilityChange);
  }

  componentWillUnmount() {
    if (this._unsubscribe) {
      this._unsubscribe();
      this._unsubscribe = null;
    }
  }

  createEvent = () => {
    const name = this.state.name !== null && this.state.name.name;
    const type = this.state.type !== null && this.state.type.type;
    const date = this.state.date.date ?
      moment.utc(moment(this.state.date.date).startOf('day')).toJSON() :
      moment.utc(moment(this.state.date).startOf('day')).toJSON();
    const time = {
      hours: this.state.time.time ? moment(this.state.time.time).hours() : moment(this.state.time).hours(),
      minutes: this.state.time.time ? moment(this.state.time.time).minutes() : moment(this.state.time).minutes(),
    };
    const url = this.state.url.url || this.state.url;
    const location = {
      address: this.state.location.details.formatted_address,
      geometry: this.state.location.details.geometry,
    };
    const description = this.state.description !== null && this.state.description.description;
    return {
      name: filter.clean(name.trim()),
      type,
      createdBy: global.firebaseApp.auth().currentUser.uid,
      date,
      time,
      url: url.toLowerCase(),
      createdInDev: __DEV__, // eslint-disable-line jsx-control-statements/jsx-jcs-no-undef
      location,
      description: description && filter.clean(description.trim()),
    };
  }

  checkDataAndPush = () => {
    let propertiesAreValid = true;

    const name = this.state.name !== null && this.state.name.name;
    const date = this.state.date.date ?
      moment.utc(moment(this.state.date.date).startOf('day')) :
      moment.utc(moment(this.state.date).startOf('day'));
    const time = {
      hours: this.state.time.time ? moment(this.state.time.time).hours() : moment(this.state.time).hours(),
      minutes: this.state.time.time ? moment(this.state.time.time).minutes() : moment(this.state.time).minutes(),
    };

    if (name !== filter.clean(name)) {
      this.props.alertWithType('error', 'Error', 'Please mind your choice of words.');
      propertiesAreValid = false;
    }
    if (!date.add(time.hours, 'hours').add(time.minutes, 'minutes').isAfter(moment().add(3, 'hours'))) {
      this.props.alertWithType('error', 'Error', 'Event should be scheduled at least three hours in advance.');
      propertiesAreValid = false;
    }
    ['name', 'date', 'time', 'url', 'type', 'description'].forEach(elem => {
      if (!this[elem].isValid()) {
        this.props.alertWithType('error', 'Error', `Event ${elem} is invalid.`);
        propertiesAreValid = false;
      }
    });
    if (propertiesAreValid) {
      this.submitEvent(this.createEvent());
    }
  }

  _blurFocusedTextInput = () => {
    TextInput.State.blurTextInput(TextInput.State.currentlyFocusedField());
  };

  _isKeyboardOpen = () => {
    return this.state.keyboardHeight > 0;
  }

  _onKeyboardVisibilityChange = (
    { keyboardHeight, layoutAnimationConfig }:
    { keyboardHeight: number, layoutAnimationConfig: ?Object }) => {
    if (keyboardHeight === 0) {
      this._blurFocusedTextInput();
    }

    if (layoutAnimationConfig) {
      LayoutAnimation.configureNext(layoutAnimationConfig);
    }

    this.setState(() => {
      return { keyboardHeight };
    });
  }

  submitEvent = (event) => {
    if (this.state.submitting) {
      this.props.alertWithType('info', 'Info', 'Your submission is in progress.');
      return;
    }

    this.setState(() => {
      return { submitting: true };
    });

    global.firebaseApp.database().ref('users')
    .child(global.firebaseApp.auth().currentUser.uid)
    .once('value')
    .then(snap => {
      const schoolUID = snap.val().school;
      global.firebaseApp.database().ref('schools')
      .child(schoolUID)
      .child('events')
      .push(event)
      .then(() => {
        // send pushes to peers
        global.firebaseApp.database().ref('users')
        .once('value')
        .then(usersSnap => {
          _.each(usersSnap.val(), user => {
            if (isExponentPushToken(user.pushToken) && user.school === schoolUID) {
              sendPushNotificationAsync({
                exponentPushToken: user.pushToken,
                message: `There's a new ${event.type.toLowerCase()} event at your school!`,
              }).catch((err) => {
                this.props.alertWithType('error', 'Error', err.toString());
              });
            }
          });

          this.props.alertWithType('success', 'Success', 'Your event was submitted successfully!');
          this.props.refresh();
          this.props.navigator.pop();
        });
      });
    })
    .catch(err => {
      this.setState(() => {
        return { submitting: false };
      });
      this.props.alertWithType('error', 'Error', err.toString());
    });
  }

  render() {
    return (
      <View
        style={ [styles.container, this.state.keyboardHeight ?
          { flex: 1, marginBottom: this.state.keyboardHeight } :
          { flex: 1 },
        ] }
        onLayout={ (e) => {
          const { width, height } = e.nativeEvent.layout;
          this.setState(() => {
            return {
              swiperHeight: height,
              swiperWidth: width,
            };
          });
        } }
      >
        <StatusBar barStyle="dark-content" />
        <Choose>
          <When condition={ this.state.submitting }>
            <ActivityIndicator size="large" />
          </When>
          <Otherwise>
            <Swiper
              ref={ r => { this.swiper = r; } }
              showsPagination
              onScroll={ this._blurFocusedTextInput }
              scrollEventThrottle={ 32 }
              keyboardShouldPersistTaps="always"
              keyboardDismissMode={ Platform.OS === 'ios' ? 'none' : 'on-drag' }
              height={ this.state.swiperHeight }
              contentContainerStyle={ styles.swiperWrapper }
              showsButtons
              loop={ false }
              prevButton={
                <Icon
                  onPress={ () => { this.swiper.scrollBy(-1, false); } }
                  name="arrow-back"
                  color={ colors.black }
                  size={ 32 }
                />
              }
              nextButton={
                <Icon
                  onPress={ () => { this.swiper.scrollBy(1, false); } }
                  name="arrow-forward"
                  color={ colors.black }
                  size={ 32 }
                />
              }
            >
              <GetEventName // attach validate() to component and access via refs
                ref={ r => { this.name = r; } }
                value={ this.state.name }
                onChange={ (name) => {
                  this.setState(() => ({ name }));
                } }
              />
              <GetEventType
                ref={ r => { this.type = r; } }
                value={ this.state.type }
                onChange={ (type) => {
                  this.setState(() => ({ type }));
                } }
              />
              <GetEventDate
                ref={ r => { this.date = r; } }
                value={ this.state.date }
                onChange={ (date) => {
                  this.setState(() => ({ date }));
                } }
              />
              <GetEventTime
                ref={ r => { this.time = r; } }
                value={ this.state.time }
                onChange={ (time) => {
                  this.setState(() => ({ time }));
                } }
              />
              <GetEventUrl
                ref={ r => { this.url = r; } }
                value={ this.state.url }
                onChange={ (url) => {
                  this.setState(() => ({ url }));
                } }
              />
              <GetEventDescription
                ref={ r => { this.description = r; } }
                value={ this.state.description }
                onChange={ (description) => {
                  this.setState(() => ({ description }));
                } }
              />
              <GetEventLocation
                location={ this.state.location }
                onLocationSelect={ (data, details = null) => {
                  this.setState(() => ({ location: { data, details } }));
                } }
                submitting={ this.state.submitting }
                onSubmit={ this.checkDataAndPush }
                keyboardHeight={ this.state.keyboardHeight }
              />
            </Swiper>
          </Otherwise>
        </Choose>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: 'white',
    justifyContent: 'center',
    alignItems: 'center',
  },
  swiperWrapper: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  slide: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});
<commit_msg>fix name bug<commit_after>import React, { Component, PropTypes } from 'react';
import {
  StatusBar,
  View,
  StyleSheet,
  LayoutAnimation,
  Platform,
  TextInput,
  ActivityIndicator,
} from 'react-native';
import {
  NavigationStyles,
} from '@exponent/ex-navigation';
import colors from '../config/colors';
import KeyboardEventListener from 'KeyboardEventListener';
import Swiper from 'react-native-swiper';
import Icon from '../components/CrossPlatformIcon';
import GetEventName from '../components/GetEventName';
import GetEventType from '../components/GetEventType';
import GetEventDate from '../components/GetEventDate';
import GetEventTime from '../components/GetEventTime';
import GetEventUrl from '../components/GetEventUrl';
import GetEventDescription from '../components/GetEventDescription';
import GetEventLocation from '../components/GetEventLocation';
import CancelButton from '../components/NavBarCancelButton';
import moment from 'moment';
import filter from '../utils/filter';
import connectDropdownAlert from '../utils/connectDropdownAlert';
import { isExponentPushToken, sendPushNotificationAsync } from '../utils/ExponentPushClient';
import _ from 'lodash';
/**
 *  Allows user to create new events for school
 */
@connectDropdownAlert
export default class NewEventScreen extends Component {
  static route = {
    navigationBar: {
      visible: true,
      title: 'NEW EVENT',
      tintColor: colors.black,
      renderLeft: () => null,
      renderRight: () => <CancelButton />,
      borderBottomColor: 'transparent',
      titleStyle: {
        fontFamily: 'open-sans-bold',
      },
      backgroundColor: 'white',
    },
    styles: {
      ...NavigationStyles.SlideHorizontal,
    },
  }

  static propTypes = {
    navigator: PropTypes.object.isRequired,
    refresh: PropTypes.func,
    alertWithType: PropTypes.func.isRequired,
  }

  state = {
    name: null,
    date: moment.utc().toDate(),
    time: moment.utc().toDate().getTime(),
    url: '',
    submitting: false,
    type: null,
    description: null,
    location: null,
    keyboardHeight: 0,
  }

  componentWillMount() {
    this._unsubscribe = KeyboardEventListener.subscribe(this._onKeyboardVisibilityChange);
  }

  componentWillUnmount() {
    if (this._unsubscribe) {
      this._unsubscribe();
      this._unsubscribe = null;
    }
  }

  createEvent = () => {
    const name = this.state.name !== null && this.state.name.name;
    const type = this.state.type !== null && this.state.type.type;
    const date = this.state.date.date ?
      moment.utc(moment(this.state.date.date).startOf('day')).toJSON() :
      moment.utc(moment(this.state.date).startOf('day')).toJSON();
    const time = {
      hours: this.state.time.time ? moment(this.state.time.time).hours() : moment(this.state.time).hours(),
      minutes: this.state.time.time ? moment(this.state.time.time).minutes() : moment(this.state.time).minutes(),
    };
    const url = this.state.url.url || this.state.url;
    const location = {
      address: this.state.location.details.formatted_address,
      geometry: this.state.location.details.geometry,
    };
    const description = this.state.description !== null && this.state.description.description;
    return {
      name: filter.clean(name.trim()),
      type,
      createdBy: global.firebaseApp.auth().currentUser.uid,
      date,
      time,
      url: url.toLowerCase(),
      createdInDev: __DEV__, // eslint-disable-line jsx-control-statements/jsx-jcs-no-undef
      location,
      description: description && filter.clean(description.trim()),
    };
  }

  checkDataAndPush = () => {
    let propertiesAreValid = true;

    const name = this.state.name !== null ? this.state.name.name : '';
    const date = this.state.date.date ?
      moment.utc(moment(this.state.date.date).startOf('day')) :
      moment.utc(moment(this.state.date).startOf('day'));
    const time = {
      hours: this.state.time.time ? moment(this.state.time.time).hours() : moment(this.state.time).hours(),
      minutes: this.state.time.time ? moment(this.state.time.time).minutes() : moment(this.state.time).minutes(),
    };

    if (name !== filter.clean(name)) {
      this.props.alertWithType('error', 'Error', 'Please mind your choice of words.');
      propertiesAreValid = false;
    }
    if (!date.add(time.hours, 'hours').add(time.minutes, 'minutes').isAfter(moment().add(3, 'hours'))) {
      this.props.alertWithType('error', 'Error', 'Event should be scheduled at least three hours in advance.');
      propertiesAreValid = false;
    }
    ['name', 'date', 'time', 'url', 'type', 'description'].forEach(elem => {
      if (!this[elem].isValid()) {
        this.props.alertWithType('error', 'Error', `Event ${elem} is invalid.`);
        propertiesAreValid = false;
      }
    });
    if (propertiesAreValid) {
      this.submitEvent(this.createEvent());
    }
  }

  _blurFocusedTextInput = () => {
    TextInput.State.blurTextInput(TextInput.State.currentlyFocusedField());
  };

  _isKeyboardOpen = () => {
    return this.state.keyboardHeight > 0;
  }

  _onKeyboardVisibilityChange = (
    { keyboardHeight, layoutAnimationConfig }:
    { keyboardHeight: number, layoutAnimationConfig: ?Object }) => {
    if (keyboardHeight === 0) {
      this._blurFocusedTextInput();
    }

    if (layoutAnimationConfig) {
      LayoutAnimation.configureNext(layoutAnimationConfig);
    }

    this.setState(() => {
      return { keyboardHeight };
    });
  }

  submitEvent = (event) => {
    if (this.state.submitting) {
      this.props.alertWithType('info', 'Info', 'Your submission is in progress.');
      return;
    }

    this.setState(() => {
      return { submitting: true };
    });

    global.firebaseApp.database().ref('users')
    .child(global.firebaseApp.auth().currentUser.uid)
    .once('value')
    .then(snap => {
      const schoolUID = snap.val().school;
      global.firebaseApp.database().ref('schools')
      .child(schoolUID)
      .child('events')
      .push(event)
      .then(() => {
        // send pushes to peers
        global.firebaseApp.database().ref('users')
        .once('value')
        .then(usersSnap => {
          _.each(usersSnap.val(), user => {
            if (isExponentPushToken(user.pushToken) && user.school === schoolUID) {
              sendPushNotificationAsync({
                exponentPushToken: user.pushToken,
                message: `There's a new ${event.type.toLowerCase()} event at your school!`,
              }).catch((err) => {
                this.props.alertWithType('error', 'Error', err.toString());
              });
            }
          });

          this.props.alertWithType('success', 'Success', 'Your event was submitted successfully!');
          this.props.refresh();
          this.props.navigator.pop();
        });
      });
    })
    .catch(err => {
      this.setState(() => {
        return { submitting: false };
      });
      this.props.alertWithType('error', 'Error', err.toString());
    });
  }

  render() {
    return (
      <View
        style={ [styles.container, this.state.keyboardHeight ?
          { flex: 1, marginBottom: this.state.keyboardHeight } :
          { flex: 1 },
        ] }
        onLayout={ (e) => {
          const { width, height } = e.nativeEvent.layout;
          this.setState(() => {
            return {
              swiperHeight: height,
              swiperWidth: width,
            };
          });
        } }
      >
        <StatusBar barStyle="dark-content" />
        <Choose>
          <When condition={ this.state.submitting }>
            <ActivityIndicator size="large" />
          </When>
          <Otherwise>
            <Swiper
              ref={ r => { this.swiper = r; } }
              showsPagination
              onScroll={ this._blurFocusedTextInput }
              scrollEventThrottle={ 32 }
              keyboardShouldPersistTaps="always"
              keyboardDismissMode={ Platform.OS === 'ios' ? 'none' : 'on-drag' }
              height={ this.state.swiperHeight }
              contentContainerStyle={ styles.swiperWrapper }
              showsButtons
              loop={ false }
              prevButton={
                <Icon
                  onPress={ () => { this.swiper.scrollBy(-1, false); } }
                  name="arrow-back"
                  color={ colors.black }
                  size={ 32 }
                />
              }
              nextButton={
                <Icon
                  onPress={ () => { this.swiper.scrollBy(1, false); } }
                  name="arrow-forward"
                  color={ colors.black }
                  size={ 32 }
                />
              }
            >
              <GetEventName // attach validate() to component and access via refs
                ref={ r => { this.name = r; } }
                value={ this.state.name }
                onChange={ (name) => {
                  this.setState(() => ({ name }));
                } }
              />
              <GetEventType
                ref={ r => { this.type = r; } }
                value={ this.state.type }
                onChange={ (type) => {
                  this.setState(() => ({ type }));
                } }
              />
              <GetEventDate
                ref={ r => { this.date = r; } }
                value={ this.state.date }
                onChange={ (date) => {
                  this.setState(() => ({ date }));
                } }
              />
              <GetEventTime
                ref={ r => { this.time = r; } }
                value={ this.state.time }
                onChange={ (time) => {
                  this.setState(() => ({ time }));
                } }
              />
              <GetEventUrl
                ref={ r => { this.url = r; } }
                value={ this.state.url }
                onChange={ (url) => {
                  this.setState(() => ({ url }));
                } }
              />
              <GetEventDescription
                ref={ r => { this.description = r; } }
                value={ this.state.description }
                onChange={ (description) => {
                  this.setState(() => ({ description }));
                } }
              />
              <GetEventLocation
                location={ this.state.location }
                onLocationSelect={ (data, details = null) => {
                  this.setState(() => ({ location: { data, details } }));
                } }
                submitting={ this.state.submitting }
                onSubmit={ this.checkDataAndPush }
                keyboardHeight={ this.state.keyboardHeight }
              />
            </Swiper>
          </Otherwise>
        </Choose>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: 'white',
    justifyContent: 'center',
    alignItems: 'center',
  },
  swiperWrapper: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  slide: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});
<|endoftext|>


<commit_before>import { currentRouteName } from '@ember/test-helpers';
import moment from 'moment';
import { module, test } from 'qunit';
import { setupAuthentication } from 'ilios-common';
import { setupApplicationTest } from 'ember-qunit';
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
import page from 'ilios/tests/pages/program';

module('Acceptance | Program - ProgramYear List', function (hooks) {
  setupApplicationTest(hooks);
  setupMirage(hooks);
  hooks.beforeEach(async function () {
    this.school = this.server.create('school');
    this.user = await setupAuthentication({ school: this.school });
    this.program = this.server.create('program', { school: this.school });
  });

  test('check list', async function (assert) {
    this.user.update({ administeredSchools: [this.school] });
    const thisYear = new Date().getFullYear();
    const cohorts = this.server.createList('cohort', 4);
    this.server.create('programYear', {
      program: this.program,
      startYear: thisYear,
      cohort: cohorts[0],
    });
    this.server.create('programYear', {
      program: this.program,
      startYear: thisYear - 2,
      cohort: cohorts[1],
    });
    this.server.create('programYear', {
      program: this.program,
      startYear: thisYear - 1,
      cohort: cohorts[2],
    });
    this.server.create('programYear', {
      program: this.program,
      startYear: thisYear - 3,
      cohort: cohorts[3],
      archived: true,
    });
    await page.visit({ programId: this.program.id });
    assert.strictEqual(page.programYears.items.length, 3);
    assert.strictEqual(page.programYears.items[0].link.text, `${thisYear - 2}`);
    assert.strictEqual(page.programYears.items[0].title, 'cohort 1');
    assert.strictEqual(page.programYears.items[1].link.text, `${thisYear - 1}`);
    assert.strictEqual(page.programYears.items[1].title, 'cohort 2');
    assert.strictEqual(page.programYears.items[2].link.text, `${thisYear}`);
    assert.strictEqual(page.programYears.items[2].title, 'cohort 0');
    await page.programYears.expandCollapse.toggle();
    assert.strictEqual(page.programYears.newProgramYear.years.options.length, 6);
  });

  test('check competencies', async function (assert) {
    const programYear = this.server.create('programYear', {
      program: this.program,
    });
    this.server.createList('competency', 5, {
      programYears: [programYear],
    });
    this.server.create('cohort', { programYear });
    await page.visit({ programId: this.program.id });
    assert.strictEqual(page.programYears.items[0].competencies.text, '5');
    assert.notOk(page.programYears.items[0].competencies.hasWarning);
  });

  test('check objectives', async function (assert) {
    const programYear = this.server.create('programYear', { program: this.program });
    this.server.createList('programYearObjective', 5, { programYear });
    this.server.create('cohort', { programYear });
    await page.visit({ programId: this.program.id });
    assert.strictEqual(page.programYears.items[0].objectives.text, '5');
    assert.notOk(page.programYears.items[0].objectives.hasWarning);
  });

  test('check directors', async function (assert) {
    const programYear = this.server.create('programYear', { program: this.program });
    this.server.createList('user', 5, {
      programYears: [programYear],
    });
    this.server.create('cohort', { programYear });
    await page.visit({ programId: this.program.id });
    assert.strictEqual(page.programYears.items[0].directors.text, '5');
    assert.notOk(page.programYears.items[0].directors.hasWarning);
  });

  test('check terms', async function (assert) {
    const vocabulary = this.server.create('vocabulary', {
      school: this.school,
    });
    const programYear = this.server.create('programYear', {
      program: this.program,
    });
    this.server.createList('term', 5, {
      programYears: [programYear],
      vocabulary,
    });
    this.server.create('cohort', { programYear });
    await page.visit({ programId: this.program.id });
    assert.strictEqual(page.programYears.items[0].terms.text, '5');
    assert.notOk(page.programYears.items[0].terms.hasWarning);
  });

  test('check warnings', async function (assert) {
    const programYear = this.server.create('programYear', {
      program: this.program,
    });
    this.server.create('cohort', { programYear });
    await page.visit({ programId: this.program.id });
    assert.ok(page.programYears.items[0].competencies.hasWarning);
    assert.ok(page.programYears.items[0].objectives.hasWarning);
    assert.ok(page.programYears.items[0].directors.hasWarning);
    assert.ok(page.programYears.items[0].terms.hasWarning);
  });

  test('check link', async function (assert) {
    const programYear = this.server.create('programYear', {
      program: this.program,
    });
    this.server.create('cohort', { programYear });
    await page.visit({ programId: this.program.id });
    await page.programYears.items[0].link.click();
    assert.strictEqual(currentRouteName(), 'program-year.index');
  });

  test('can delete a program-year', async function (assert) {
    this.user.update({ administeredSchools: [this.school] });
    const programYear = this.server.create('programYear', {
      program: this.program,
    });
    this.server.create('cohort', { programYear });
    await page.visit({ programId: this.program.id });
    assert.strictEqual(page.programYears.items.length, 1);
    await page.programYears.items[0].remove();
    await page.programYears.items[0].confirmRemoval.confirm();
    assert.strictEqual(page.programYears.items.length, 0);
  });

  test('canceling adding new program-year collapses new program year form', async function (assert) {
    this.user.update({ administeredSchools: [this.school] });
    await page.visit({ programId: this.program.id });
    assert.notOk(page.programYears.newProgramYear.isVisible);
    await page.programYears.expandCollapse.toggle();
    assert.ok(page.programYears.newProgramYear.isVisible);
    await page.programYears.newProgramYear.cancel.click();
    assert.notOk(page.programYears.newProgramYear.isVisible);
  });

  test('can add a program-year (with no pre-existing program-years)', async function (assert) {
    const thisYear = new Date().getFullYear();
    this.user.update({ administeredSchools: [this.school] });
    await page.visit({ programId: this.program.id });
    assert.strictEqual(page.programYears.items.length, 0);
    await page.programYears.expandCollapse.toggle();
    await page.programYears.newProgramYear.years.select(thisYear);
    await page.programYears.newProgramYear.done.click();
    assert.strictEqual(parseInt(page.programYears.items[0].link.text, 10), thisYear);
    assert.strictEqual(page.programYears.items[0].title, `Class of ${thisYear + 4}`);
    assert.ok(page.programYears.items[0].competencies.hasWarning);
    assert.ok(page.programYears.items[0].objectives.hasWarning);
    assert.ok(page.programYears.items[0].directors.hasWarning);
    assert.ok(page.programYears.items[0].terms.hasWarning);
  });

  test('can add a program-year (with pre-existing program-year)', async function (assert) {
    const thisYear = new Date().getFullYear();
    this.user.update({ administeredSchools: [this.school] });
    const directors = this.server.createList('user', 3);
    const competencies = this.server.createList('competency', 3);
    const vocabulary = this.server.create('vocabulary', { school: this.school });
    const terms = this.server.createList('term', 3, { vocabulary });
    const currentYear = parseInt(moment().format('YYYY'), 10);
    const programYear = this.server.create('programYear', {
      program: this.program,
      startYear: currentYear,
      directors,
      competencies,
      terms,
    });
    this.server.create('cohort', { programYear });
    this.server.createList('programYearObjective', 2, { programYear });
    const ancestor = this.server.create('programYearObjective');
    this.server.create('programYearObjective', { programYear, ancestor });
    await page.visit({ programId: this.program.id });
    assert.strictEqual(page.programYears.items.length, 1);
    assert.strictEqual(parseInt(page.programYears.items[0].link.text, 10), thisYear);
    assert.strictEqual(page.programYears.items[0].title, 'cohort 0');
    assert.strictEqual(page.programYears.items[0].competencies.text, '3');
    assert.strictEqual(page.programYears.items[0].objectives.text, '3');
    assert.strictEqual(page.programYears.items[0].directors.text, '3');
    assert.strictEqual(page.programYears.items[0].terms.text, '3');
    await page.programYears.expandCollapse.toggle();
    assert.strictEqual(page.programYears.newProgramYear.years.options.length, 9);
    await page.programYears.newProgramYear.years.select(thisYear + 1);
    await page.programYears.newProgramYear.done.click();
    assert.strictEqual(page.programYears.items.length, 2);
    assert.strictEqual(parseInt(page.programYears.items[1].link.text, 10), thisYear + 1);
    assert.strictEqual(page.programYears.items[1].title, `Class of ${(thisYear + 5).toString()}`);
    assert.strictEqual(page.programYears.items[1].competencies.text, '3');
    assert.strictEqual(page.programYears.items[1].objectives.text, '3');
    assert.strictEqual(page.programYears.items[1].directors.text, '3');
    assert.strictEqual(page.programYears.items[1].terms.text, '3');
    await page.programYears.expandCollapse.toggle();
    assert.strictEqual(page.programYears.newProgramYear.years.options.length, 8);
  });

  test('privileged users can lock and unlock program-year', async function (assert) {
    this.user.update({ administeredSchools: [this.school] });
    const cohorts = this.server.createList('cohort', 2);
    this.server.create('programYear', {
      program: this.program,
      startYear: 2014,
      cohort: cohorts[0],
      locked: true,
      directorIds: [this.user.id],
    });
    this.server.create('programYear', {
      program: this.program,
      startYear: 2015,
      cohort: cohorts[1],
      locked: false,
      directorIds: [this.user.id],
    });
    await page.visit({ programId: this.program.id });
    assert.strictEqual(page.programYears.items.length, 2);
    assert.ok(page.programYears.items[0].isLocked);
    assert.ok(page.programYears.items[1].isUnlocked);
    await page.programYears.items[0].unlock();
    await page.programYears.items[1].lock();
    assert.ok(page.programYears.items[0].isUnlocked);
    assert.ok(page.programYears.items[1].isLocked);
  });

  test('delete-button is not visible for program years with populated cohorts', async function (assert) {
    this.user.update({ administeredSchools: [this.school] });
    const programYears = this.server.createList('programYear', 2, {
      program: this.program,
    });
    const cohort = this.server.create('cohort', {
      programYear: programYears[0],
    });
    this.server.create('cohort', {
      programYear: programYears[1],
    });
    this.server.create('user', {
      cohorts: [cohort],
    });
    await page.visit({ programId: this.program.id });
    assert.notOk(page.programYears.items[0].canBeRemoved);
    assert.ok(page.programYears.items[1].canBeRemoved);
  });
});
<commit_msg>Replace moment with luxon in test<commit_after>import { currentRouteName } from '@ember/test-helpers';
import { DateTime } from 'luxon';
import { module, test } from 'qunit';
import { setupAuthentication } from 'ilios-common';
import { setupApplicationTest } from 'ember-qunit';
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
import page from 'ilios/tests/pages/program';

module('Acceptance | Program - ProgramYear List', function (hooks) {
  setupApplicationTest(hooks);
  setupMirage(hooks);
  hooks.beforeEach(async function () {
    this.school = this.server.create('school');
    this.user = await setupAuthentication({ school: this.school });
    this.program = this.server.create('program', { school: this.school });
  });

  test('check list', async function (assert) {
    this.user.update({ administeredSchools: [this.school] });
    const thisYear = new Date().getFullYear();
    const cohorts = this.server.createList('cohort', 4);
    this.server.create('programYear', {
      program: this.program,
      startYear: thisYear,
      cohort: cohorts[0],
    });
    this.server.create('programYear', {
      program: this.program,
      startYear: thisYear - 2,
      cohort: cohorts[1],
    });
    this.server.create('programYear', {
      program: this.program,
      startYear: thisYear - 1,
      cohort: cohorts[2],
    });
    this.server.create('programYear', {
      program: this.program,
      startYear: thisYear - 3,
      cohort: cohorts[3],
      archived: true,
    });
    await page.visit({ programId: this.program.id });
    assert.strictEqual(page.programYears.items.length, 3);
    assert.strictEqual(page.programYears.items[0].link.text, `${thisYear - 2}`);
    assert.strictEqual(page.programYears.items[0].title, 'cohort 1');
    assert.strictEqual(page.programYears.items[1].link.text, `${thisYear - 1}`);
    assert.strictEqual(page.programYears.items[1].title, 'cohort 2');
    assert.strictEqual(page.programYears.items[2].link.text, `${thisYear}`);
    assert.strictEqual(page.programYears.items[2].title, 'cohort 0');
    await page.programYears.expandCollapse.toggle();
    assert.strictEqual(page.programYears.newProgramYear.years.options.length, 6);
  });

  test('check competencies', async function (assert) {
    const programYear = this.server.create('programYear', {
      program: this.program,
    });
    this.server.createList('competency', 5, {
      programYears: [programYear],
    });
    this.server.create('cohort', { programYear });
    await page.visit({ programId: this.program.id });
    assert.strictEqual(page.programYears.items[0].competencies.text, '5');
    assert.notOk(page.programYears.items[0].competencies.hasWarning);
  });

  test('check objectives', async function (assert) {
    const programYear = this.server.create('programYear', { program: this.program });
    this.server.createList('programYearObjective', 5, { programYear });
    this.server.create('cohort', { programYear });
    await page.visit({ programId: this.program.id });
    assert.strictEqual(page.programYears.items[0].objectives.text, '5');
    assert.notOk(page.programYears.items[0].objectives.hasWarning);
  });

  test('check directors', async function (assert) {
    const programYear = this.server.create('programYear', { program: this.program });
    this.server.createList('user', 5, {
      programYears: [programYear],
    });
    this.server.create('cohort', { programYear });
    await page.visit({ programId: this.program.id });
    assert.strictEqual(page.programYears.items[0].directors.text, '5');
    assert.notOk(page.programYears.items[0].directors.hasWarning);
  });

  test('check terms', async function (assert) {
    const vocabulary = this.server.create('vocabulary', {
      school: this.school,
    });
    const programYear = this.server.create('programYear', {
      program: this.program,
    });
    this.server.createList('term', 5, {
      programYears: [programYear],
      vocabulary,
    });
    this.server.create('cohort', { programYear });
    await page.visit({ programId: this.program.id });
    assert.strictEqual(page.programYears.items[0].terms.text, '5');
    assert.notOk(page.programYears.items[0].terms.hasWarning);
  });

  test('check warnings', async function (assert) {
    const programYear = this.server.create('programYear', {
      program: this.program,
    });
    this.server.create('cohort', { programYear });
    await page.visit({ programId: this.program.id });
    assert.ok(page.programYears.items[0].competencies.hasWarning);
    assert.ok(page.programYears.items[0].objectives.hasWarning);
    assert.ok(page.programYears.items[0].directors.hasWarning);
    assert.ok(page.programYears.items[0].terms.hasWarning);
  });

  test('check link', async function (assert) {
    const programYear = this.server.create('programYear', {
      program: this.program,
    });
    this.server.create('cohort', { programYear });
    await page.visit({ programId: this.program.id });
    await page.programYears.items[0].link.click();
    assert.strictEqual(currentRouteName(), 'program-year.index');
  });

  test('can delete a program-year', async function (assert) {
    this.user.update({ administeredSchools: [this.school] });
    const programYear = this.server.create('programYear', {
      program: this.program,
    });
    this.server.create('cohort', { programYear });
    await page.visit({ programId: this.program.id });
    assert.strictEqual(page.programYears.items.length, 1);
    await page.programYears.items[0].remove();
    await page.programYears.items[0].confirmRemoval.confirm();
    assert.strictEqual(page.programYears.items.length, 0);
  });

  test('canceling adding new program-year collapses new program year form', async function (assert) {
    this.user.update({ administeredSchools: [this.school] });
    await page.visit({ programId: this.program.id });
    assert.notOk(page.programYears.newProgramYear.isVisible);
    await page.programYears.expandCollapse.toggle();
    assert.ok(page.programYears.newProgramYear.isVisible);
    await page.programYears.newProgramYear.cancel.click();
    assert.notOk(page.programYears.newProgramYear.isVisible);
  });

  test('can add a program-year (with no pre-existing program-years)', async function (assert) {
    const thisYear = new Date().getFullYear();
    this.user.update({ administeredSchools: [this.school] });
    await page.visit({ programId: this.program.id });
    assert.strictEqual(page.programYears.items.length, 0);
    await page.programYears.expandCollapse.toggle();
    await page.programYears.newProgramYear.years.select(thisYear);
    await page.programYears.newProgramYear.done.click();
    assert.strictEqual(parseInt(page.programYears.items[0].link.text, 10), thisYear);
    assert.strictEqual(page.programYears.items[0].title, `Class of ${thisYear + 4}`);
    assert.ok(page.programYears.items[0].competencies.hasWarning);
    assert.ok(page.programYears.items[0].objectives.hasWarning);
    assert.ok(page.programYears.items[0].directors.hasWarning);
    assert.ok(page.programYears.items[0].terms.hasWarning);
  });

  test('can add a program-year (with pre-existing program-year)', async function (assert) {
    const thisYear = new Date().getFullYear();
    this.user.update({ administeredSchools: [this.school] });
    const directors = this.server.createList('user', 3);
    const competencies = this.server.createList('competency', 3);
    const vocabulary = this.server.create('vocabulary', { school: this.school });
    const terms = this.server.createList('term', 3, { vocabulary });
    const currentYear = DateTime.now().year;
    const programYear = this.server.create('programYear', {
      program: this.program,
      startYear: currentYear,
      directors,
      competencies,
      terms,
    });
    this.server.create('cohort', { programYear });
    this.server.createList('programYearObjective', 2, { programYear });
    const ancestor = this.server.create('programYearObjective');
    this.server.create('programYearObjective', { programYear, ancestor });
    await page.visit({ programId: this.program.id });
    assert.strictEqual(page.programYears.items.length, 1);
    assert.strictEqual(parseInt(page.programYears.items[0].link.text, 10), thisYear);
    assert.strictEqual(page.programYears.items[0].title, 'cohort 0');
    assert.strictEqual(page.programYears.items[0].competencies.text, '3');
    assert.strictEqual(page.programYears.items[0].objectives.text, '3');
    assert.strictEqual(page.programYears.items[0].directors.text, '3');
    assert.strictEqual(page.programYears.items[0].terms.text, '3');
    await page.programYears.expandCollapse.toggle();
    assert.strictEqual(page.programYears.newProgramYear.years.options.length, 9);
    await page.programYears.newProgramYear.years.select(thisYear + 1);
    await page.programYears.newProgramYear.done.click();
    assert.strictEqual(page.programYears.items.length, 2);
    assert.strictEqual(parseInt(page.programYears.items[1].link.text, 10), thisYear + 1);
    assert.strictEqual(page.programYears.items[1].title, `Class of ${(thisYear + 5).toString()}`);
    assert.strictEqual(page.programYears.items[1].competencies.text, '3');
    assert.strictEqual(page.programYears.items[1].objectives.text, '3');
    assert.strictEqual(page.programYears.items[1].directors.text, '3');
    assert.strictEqual(page.programYears.items[1].terms.text, '3');
    await page.programYears.expandCollapse.toggle();
    assert.strictEqual(page.programYears.newProgramYear.years.options.length, 8);
  });

  test('privileged users can lock and unlock program-year', async function (assert) {
    this.user.update({ administeredSchools: [this.school] });
    const cohorts = this.server.createList('cohort', 2);
    this.server.create('programYear', {
      program: this.program,
      startYear: 2014,
      cohort: cohorts[0],
      locked: true,
      directorIds: [this.user.id],
    });
    this.server.create('programYear', {
      program: this.program,
      startYear: 2015,
      cohort: cohorts[1],
      locked: false,
      directorIds: [this.user.id],
    });
    await page.visit({ programId: this.program.id });
    assert.strictEqual(page.programYears.items.length, 2);
    assert.ok(page.programYears.items[0].isLocked);
    assert.ok(page.programYears.items[1].isUnlocked);
    await page.programYears.items[0].unlock();
    await page.programYears.items[1].lock();
    assert.ok(page.programYears.items[0].isUnlocked);
    assert.ok(page.programYears.items[1].isLocked);
  });

  test('delete-button is not visible for program years with populated cohorts', async function (assert) {
    this.user.update({ administeredSchools: [this.school] });
    const programYears = this.server.createList('programYear', 2, {
      program: this.program,
    });
    const cohort = this.server.create('cohort', {
      programYear: programYears[0],
    });
    this.server.create('cohort', {
      programYear: programYears[1],
    });
    this.server.create('user', {
      cohorts: [cohort],
    });
    await page.visit({ programId: this.program.id });
    assert.notOk(page.programYears.items[0].canBeRemoved);
    assert.ok(page.programYears.items[1].canBeRemoved);
  });
});
<|endoftext|>