<commit_before>/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 New Vector Ltd
Copyright 2018 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import classNames from 'classnames';
import dis from '../../../dispatcher';
import MatrixClientPeg from '../../../MatrixClientPeg';
import DMRoomMap from '../../../utils/DMRoomMap';
import sdk from '../../../index';
import {ContextMenu, ContextMenuButton, toRightOf} from '../../structures/ContextMenu';
import * as RoomNotifs from '../../../RoomNotifs';
import * as FormattingUtils from '../../../utils/FormattingUtils';
import ActiveRoomObserver from '../../../ActiveRoomObserver';
import RoomViewStore from '../../../stores/RoomViewStore';
import SettingsStore from "../../../settings/SettingsStore";
import {_t} from "../../../languageHandler";

module.exports = createReactClass({
    displayName: 'RoomTile',

    propTypes: {
        onClick: PropTypes.func,

        room: PropTypes.object.isRequired,
        collapsed: PropTypes.bool.isRequired,
        unread: PropTypes.bool.isRequired,
        highlight: PropTypes.bool.isRequired,
        // If true, apply mx_RoomTile_transparent class
        transparent: PropTypes.bool,
        isInvite: PropTypes.bool.isRequired,
        incomingCall: PropTypes.object,
    },

    getDefaultProps: function() {
        return {
            isDragging: false,
        };
    },

    getInitialState: function() {
         // eslint-disable-next-line camelcase
        const { event: { content: { join_rule } } } =
          this.props.room.currentState.getStateEvents("m.room.join_rules", "");

        return ({
            join_rule,
            hover: false,
            badgeHover: false,
            contextMenuPosition: null, // DOM bounding box, null if non-shown
            roomName: this.props.room.name,
            notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
            notificationCount: this.props.room.getUnreadNotificationCount(),
            selected: this.props.room.roomId === RoomViewStore.getRoomId(),
            statusMessage: this._getStatusMessage(),
        });
    },

    _shouldShowStatusMessage() {
        if (!SettingsStore.isFeatureEnabled("feature_custom_status")) {
            return false;
        }
        const isInvite = this.props.room.getMyMembership() === "invite";
        const isJoined = this.props.room.getMyMembership() === "join";
        const looksLikeDm = this.props.room.getInvitedAndJoinedMemberCount() === 2;
        return !isInvite && isJoined && looksLikeDm;
    },

    _getStatusMessageUser() {
        if (!MatrixClientPeg.get()) return null; // We've probably been logged out

        const selfId = MatrixClientPeg.get().getUserId();
        const otherMember = this.props.room.currentState.getMembersExcept([selfId])[0];
        if (!otherMember) {
            return null;
        }
        return otherMember.user;
    },

    _getStatusMessage() {
        const statusUser = this._getStatusMessageUser();
        if (!statusUser) {
            return "";
        }
        return statusUser._unstable_statusMessage;
    },

    onRoomName: function(room) {
        if (room !== this.props.room) return;
        this.setState({
            roomName: this.props.room.name,
        });
    },

    onJoinRule: function(ev) {
        /* eslint-disable camelcase */
        const { event: { type, room_id } } = ev;
        if (type !== "m.room.join_rules") return;
        if (room_id !== this.props.room.roomId) return;
        const { event: { content: { join_rule } } } = ev;
        this.setState({ join_rule });
        /* eslint-enable camelcase */
    },

    onAccountData: function(accountDataEvent) {
        if (accountDataEvent.getType() === 'm.push_rules') {
            this.setState({
                notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
            });
        }
    },

    onAction: function(payload) {
        switch (payload.action) {
            // XXX: slight hack in order to zero the notification count when a room
            // is read. Ideally this state would be given to this via props (as we
            // do with `unread`). This is still better than forceUpdating the entire
            // RoomList when a room is read.
            case 'on_room_read':
                if (payload.roomId !== this.props.room.roomId) break;
                this.setState({
                    notificationCount: this.props.room.getUnreadNotificationCount(),
                });
                break;
            // RoomTiles are one of the few components that may show custom status and
            // also remain on screen while in Settings toggling the feature.  This ensures
            // you can clearly see the status hide and show when toggling the feature.
            case 'feature_custom_status_changed':
                this.forceUpdate();
                break;
        }
    },

    _onActiveRoomChange: function() {
        this.setState({
            selected: this.props.room.roomId === RoomViewStore.getRoomId(),
        });
    },

    componentDidMount: function() {
        const cli = MatrixClientPeg.get();
        cli.on("accountData", this.onAccountData);
        cli.on("Room.name", this.onRoomName);
        cli.on("RoomState.events", this.onJoinRule);
        ActiveRoomObserver.addListener(this.props.room.roomId, this._onActiveRoomChange);
        this.dispatcherRef = dis.register(this.onAction);

        if (this._shouldShowStatusMessage()) {
            const statusUser = this._getStatusMessageUser();
            if (statusUser) {
                statusUser.on("User._unstable_statusMessage", this._onStatusMessageCommitted);
            }
        }
    },

    componentWillUnmount: function() {
        const cli = MatrixClientPeg.get();
        if (cli) {
            MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
            MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
            cli.removeListener("RoomState.events", this.onJoinRule);
        }
        ActiveRoomObserver.removeListener(this.props.room.roomId, this._onActiveRoomChange);
        dis.unregister(this.dispatcherRef);

        if (this._shouldShowStatusMessage()) {
            const statusUser = this._getStatusMessageUser();
            if (statusUser) {
                statusUser.removeListener(
                    "User._unstable_statusMessage",
                    this._onStatusMessageCommitted,
                );
            }
        }
    },

    componentWillReceiveProps: function(props) {
        // XXX: This could be a lot better - this makes the assumption that
        // the notification count may have changed when the properties of
        // the room tile change.
        this.setState({
            notificationCount: this.props.room.getUnreadNotificationCount(),
        });
    },

    // Do a simple shallow comparison of props and state to avoid unnecessary
    // renders. The assumption made here is that only state and props are used
    // in rendering this component and children.
    //
    // RoomList is frequently made to forceUpdate, so this decreases number of
    // RoomTile renderings.
    shouldComponentUpdate: function(newProps, newState) {
        if (Object.keys(newProps).some((k) => newProps[k] !== this.props[k])) {
            return true;
        }
        if (Object.keys(newState).some((k) => newState[k] !== this.state[k])) {
            return true;
        }
        return false;
    },

    _onStatusMessageCommitted() {
        // The status message `User` object has observed a message change.
        this.setState({
            statusMessage: this._getStatusMessage(),
        });
    },

    onClick: function(ev) {
        if (this.props.onClick) {
            this.props.onClick(this.props.room.roomId, ev);
        }
    },

    onMouseEnter: function() {
        this.setState( { hover: true });
        this.badgeOnMouseEnter();
    },

    onMouseLeave: function() {
        this.setState( { hover: false });
        this.badgeOnMouseLeave();
    },

    badgeOnMouseEnter: function() {
        // Only allow non-guests to access the context menu
        // and only change it if it needs to change
        if (!MatrixClientPeg.get().isGuest() && !this.state.badgeHover) {
            this.setState( { badgeHover: true } );
        }
    },

    badgeOnMouseLeave: function() {
        this.setState( { badgeHover: false } );
    },

    _showContextMenu: function(boundingClientRect) {
        // Only allow non-guests to access the context menu
        if (MatrixClientPeg.get().isGuest()) return;

        const state = {
            contextMenuPosition: boundingClientRect,
        };

        // If the badge is clicked, then no longer show tooltip
        if (this.props.collapsed) {
            state.hover = false;
        }

        this.setState(state);
    },

    onContextMenuButtonClick: function(e) {
        // Prevent the RoomTile onClick event firing as well
        e.stopPropagation();
        e.preventDefault();

        this._showContextMenu(e.target.getBoundingClientRect());
    },

    onContextMenu: function(e) {
        // Prevent the native context menu
        e.preventDefault();

        this._showContextMenu({
            right: e.clientX,
            top: e.clientY,
            height: 0,
        });
    },

    closeMenu: function() {
        this.setState({
            contextMenuPosition: null,
        });
        this.props.refreshSubList();
    },

    render: function() {
        const isInvite = this.props.room.getMyMembership() === "invite";
        const notificationCount = this.props.notificationCount;
        // var highlightCount = this.props.room.getUnreadNotificationCount("highlight");

        const notifBadges = notificationCount > 0 && RoomNotifs.shouldShowNotifBadge(this.state.notifState);
        const mentionBadges = this.props.highlight && RoomNotifs.shouldShowMentionBadge(this.state.notifState);
        const badges = notifBadges || mentionBadges;

        let subtext = null;
        if (this._shouldShowStatusMessage()) {
            subtext = this.state.statusMessage;
        }

        const isMenuDisplayed = Boolean(this.state.contextMenuPosition);

        const classes = classNames({
            'mx_RoomTile': true,
            'mx_RoomTile_selected': this.state.selected,
            'mx_RoomTile_unread': this.props.unread,
            'mx_RoomTile_unreadNotify': notifBadges,
            'mx_RoomTile_highlight': mentionBadges,
            'mx_RoomTile_invited': isInvite,
            'mx_RoomTile_menuDisplayed': isMenuDisplayed,
            'mx_RoomTile_noBadges': !badges,
            'mx_RoomTile_transparent': this.props.transparent,
            'mx_RoomTile_hasSubtext': subtext && !this.props.collapsed,
            'mx_RoomTile_isPrivate': this.state.join_rule == "invite",
        });

        const avatarClasses = classNames({
            'mx_RoomTile_avatar': true,
        });

        const badgeClasses = classNames({
            'mx_RoomTile_badge': true,
            'mx_RoomTile_badgeButton': this.state.badgeHover || isMenuDisplayed,
        });

        let name = this.state.roomName;
        if (name == undefined || name == null) name = '';
        name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon

        let badge;
        if (badges) {
            const limitedCount = FormattingUtils.formatCount(notificationCount);
            const badgeContent = notificationCount ? limitedCount : '!';
            badge =
{ badgeContent }
;
        }

        let label;
        let subtextLabel;
        let tooltip;
        if (!this.props.collapsed) {
            const nameClasses = classNames({
                'mx_RoomTile_name': true,
                'mx_RoomTile_invite': this.props.isInvite,
                'mx_RoomTile_badgeShown': badges || this.state.badgeHover || isMenuDisplayed,
            });

            subtextLabel = subtext ? { subtext } : null;
            label =
{ name }
;
        } else if (this.state.hover) {
            const Tooltip = sdk.getComponent("elements.Tooltip");
            tooltip = <Tooltip className="mx_RoomTile_tooltip" label={this.props.room.name} dir="auto" />;
        }

        //var incomingCallBox;
        //if (this.props.incomingCall) {
        //    var IncomingCallBox = sdk.getComponent("voip.IncomingCallBox");
        //    incomingCallBox = <IncomingCallBox incomingCall={ this.props.incomingCall }/>;
        //}

        const AccessibleButton = sdk.getComponent('elements.AccessibleButton');

        let contextMenuButton;
        if (!MatrixClientPeg.get().isGuest()) {
            contextMenuButton = (
                <ContextMenuButton
                    className="mx_RoomTile_menuButton"
                    label={_t("Options")}
                    isExpanded={isMenuDisplayed}
                    onClick={this.onContextMenuButtonClick} />
            );
        }

        const RoomAvatar = sdk.getComponent('avatars.RoomAvatar');

        let ariaLabel = name;

        const dmUserId = DMRoomMap.shared().getUserIdForRoomId(this.props.room.roomId);

        let dmIndicator;
        let dmOnline;
        if (dmUserId) {
            dmIndicator = ;

            const { room } = this.props;
            const member = room.getMember(dmUserId);
            if (member && member.membership === "join" && room.getJoinedMemberCount() === 2) {
                const UserOnlineDot = sdk.getComponent('rooms.UserOnlineDot');
                dmOnline = <UserOnlineDot userId={dmUserId} />;
            }
        }

        // The following labels are written in such a fashion to increase screen reader efficiency (speed).
        if (notifBadges && mentionBadges && !isInvite) {
            ariaLabel += " " + _t("%(count)s unread messages including mentions.", {
                count: notificationCount,
            });
        } else if (notifBadges) {
            ariaLabel += " " + _t("%(count)s unread messages.", { count: notificationCount });
        } else if (mentionBadges && !isInvite) {
            ariaLabel += " " + _t("Unread mentions.");
        } else if (this.props.unread) {
            ariaLabel += " " + _t("Unread messages.");
        }

        let contextMenu;
        if (isMenuDisplayed) {
            const RoomTileContextMenu = sdk.getComponent('context_menus.RoomTileContextMenu');
            contextMenu = (
                <ContextMenu {...toRightOf(this.state.contextMenuPosition)} onFinished={this.closeMenu}>
                    <RoomTileContextMenu room={this.props.room} onFinished={this.closeMenu} />
                </ContextMenu>
            );
        }

        return <React.Fragment>
            <AccessibleButton
                tabIndex="0"
                className={classes}
                onClick={this.onClick}
                onMouseEnter={this.onMouseEnter}
                onMouseLeave={this.onMouseLeave}
                onContextMenu={this.onContextMenu}
                aria-label={ariaLabel}
                aria-selected={this.state.selected}
                role="treeitem"
            >

                        <RoomAvatar room={this.props.room} width={24} height={24} />
                        { dmIndicator }

                        { label }
                        { subtextLabel }

                    { dmOnline }
                    { contextMenuButton }
                    { badge }

                { /* { incomingCallBox } */ }
                { tooltip }
            </AccessibleButton>

            { contextMenu }
        </React.Fragment>;
    },
});
<commit_msg>suppress private icon if the room is a DM<commit_after>/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 New Vector Ltd
Copyright 2018 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import classNames from 'classnames';
import dis from '../../../dispatcher';
import MatrixClientPeg from '../../../MatrixClientPeg';
import DMRoomMap from '../../../utils/DMRoomMap';
import sdk from '../../../index';
import {ContextMenu, ContextMenuButton, toRightOf} from '../../structures/ContextMenu';
import * as RoomNotifs from '../../../RoomNotifs';
import * as FormattingUtils from '../../../utils/FormattingUtils';
import ActiveRoomObserver from '../../../ActiveRoomObserver';
import RoomViewStore from '../../../stores/RoomViewStore';
import SettingsStore from "../../../settings/SettingsStore";
import {_t} from "../../../languageHandler";

module.exports = createReactClass({
    displayName: 'RoomTile',

    propTypes: {
        onClick: PropTypes.func,

        room: PropTypes.object.isRequired,
        collapsed: PropTypes.bool.isRequired,
        unread: PropTypes.bool.isRequired,
        highlight: PropTypes.bool.isRequired,
        // If true, apply mx_RoomTile_transparent class
        transparent: PropTypes.bool,
        isInvite: PropTypes.bool.isRequired,
        incomingCall: PropTypes.object,
    },

    getDefaultProps: function() {
        return {
            isDragging: false,
        };
    },

    getInitialState: function() {
         // eslint-disable-next-line camelcase
        const { event: { content: { join_rule } } } =
          this.props.room.currentState.getStateEvents("m.room.join_rules", "");

        return ({
            join_rule,
            hover: false,
            badgeHover: false,
            contextMenuPosition: null, // DOM bounding box, null if non-shown
            roomName: this.props.room.name,
            notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
            notificationCount: this.props.room.getUnreadNotificationCount(),
            selected: this.props.room.roomId === RoomViewStore.getRoomId(),
            statusMessage: this._getStatusMessage(),
        });
    },

    _shouldShowStatusMessage() {
        if (!SettingsStore.isFeatureEnabled("feature_custom_status")) {
            return false;
        }
        const isInvite = this.props.room.getMyMembership() === "invite";
        const isJoined = this.props.room.getMyMembership() === "join";
        const looksLikeDm = this.props.room.getInvitedAndJoinedMemberCount() === 2;
        return !isInvite && isJoined && looksLikeDm;
    },

    _getStatusMessageUser() {
        if (!MatrixClientPeg.get()) return null; // We've probably been logged out

        const selfId = MatrixClientPeg.get().getUserId();
        const otherMember = this.props.room.currentState.getMembersExcept([selfId])[0];
        if (!otherMember) {
            return null;
        }
        return otherMember.user;
    },

    _getStatusMessage() {
        const statusUser = this._getStatusMessageUser();
        if (!statusUser) {
            return "";
        }
        return statusUser._unstable_statusMessage;
    },

    onRoomName: function(room) {
        if (room !== this.props.room) return;
        this.setState({
            roomName: this.props.room.name,
        });
    },

    onJoinRule: function(ev) {
        /* eslint-disable camelcase */
        const { event: { type, room_id } } = ev;
        if (type !== "m.room.join_rules") return;
        if (room_id !== this.props.room.roomId) return;
        const { event: { content: { join_rule } } } = ev;
        this.setState({ join_rule });
        /* eslint-enable camelcase */
    },

    onAccountData: function(accountDataEvent) {
        if (accountDataEvent.getType() === 'm.push_rules') {
            this.setState({
                notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
            });
        }
    },

    onAction: function(payload) {
        switch (payload.action) {
            // XXX: slight hack in order to zero the notification count when a room
            // is read. Ideally this state would be given to this via props (as we
            // do with `unread`). This is still better than forceUpdating the entire
            // RoomList when a room is read.
            case 'on_room_read':
                if (payload.roomId !== this.props.room.roomId) break;
                this.setState({
                    notificationCount: this.props.room.getUnreadNotificationCount(),
                });
                break;
            // RoomTiles are one of the few components that may show custom status and
            // also remain on screen while in Settings toggling the feature.  This ensures
            // you can clearly see the status hide and show when toggling the feature.
            case 'feature_custom_status_changed':
                this.forceUpdate();
                break;
        }
    },

    _onActiveRoomChange: function() {
        this.setState({
            selected: this.props.room.roomId === RoomViewStore.getRoomId(),
        });
    },

    componentDidMount: function() {
        const cli = MatrixClientPeg.get();
        cli.on("accountData", this.onAccountData);
        cli.on("Room.name", this.onRoomName);
        cli.on("RoomState.events", this.onJoinRule);
        ActiveRoomObserver.addListener(this.props.room.roomId, this._onActiveRoomChange);
        this.dispatcherRef = dis.register(this.onAction);

        if (this._shouldShowStatusMessage()) {
            const statusUser = this._getStatusMessageUser();
            if (statusUser) {
                statusUser.on("User._unstable_statusMessage", this._onStatusMessageCommitted);
            }
        }
    },

    componentWillUnmount: function() {
        const cli = MatrixClientPeg.get();
        if (cli) {
            MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
            MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
            cli.removeListener("RoomState.events", this.onJoinRule);
        }
        ActiveRoomObserver.removeListener(this.props.room.roomId, this._onActiveRoomChange);
        dis.unregister(this.dispatcherRef);

        if (this._shouldShowStatusMessage()) {
            const statusUser = this._getStatusMessageUser();
            if (statusUser) {
                statusUser.removeListener(
                    "User._unstable_statusMessage",
                    this._onStatusMessageCommitted,
                );
            }
        }
    },

    componentWillReceiveProps: function(props) {
        // XXX: This could be a lot better - this makes the assumption that
        // the notification count may have changed when the properties of
        // the room tile change.
        this.setState({
            notificationCount: this.props.room.getUnreadNotificationCount(),
        });
    },

    // Do a simple shallow comparison of props and state to avoid unnecessary
    // renders. The assumption made here is that only state and props are used
    // in rendering this component and children.
    //
    // RoomList is frequently made to forceUpdate, so this decreases number of
    // RoomTile renderings.
    shouldComponentUpdate: function(newProps, newState) {
        if (Object.keys(newProps).some((k) => newProps[k] !== this.props[k])) {
            return true;
        }
        if (Object.keys(newState).some((k) => newState[k] !== this.state[k])) {
            return true;
        }
        return false;
    },

    _onStatusMessageCommitted() {
        // The status message `User` object has observed a message change.
        this.setState({
            statusMessage: this._getStatusMessage(),
        });
    },

    onClick: function(ev) {
        if (this.props.onClick) {
            this.props.onClick(this.props.room.roomId, ev);
        }
    },

    onMouseEnter: function() {
        this.setState( { hover: true });
        this.badgeOnMouseEnter();
    },

    onMouseLeave: function() {
        this.setState( { hover: false });
        this.badgeOnMouseLeave();
    },

    badgeOnMouseEnter: function() {
        // Only allow non-guests to access the context menu
        // and only change it if it needs to change
        if (!MatrixClientPeg.get().isGuest() && !this.state.badgeHover) {
            this.setState( { badgeHover: true } );
        }
    },

    badgeOnMouseLeave: function() {
        this.setState( { badgeHover: false } );
    },

    _showContextMenu: function(boundingClientRect) {
        // Only allow non-guests to access the context menu
        if (MatrixClientPeg.get().isGuest()) return;

        const state = {
            contextMenuPosition: boundingClientRect,
        };

        // If the badge is clicked, then no longer show tooltip
        if (this.props.collapsed) {
            state.hover = false;
        }

        this.setState(state);
    },

    onContextMenuButtonClick: function(e) {
        // Prevent the RoomTile onClick event firing as well
        e.stopPropagation();
        e.preventDefault();

        this._showContextMenu(e.target.getBoundingClientRect());
    },

    onContextMenu: function(e) {
        // Prevent the native context menu
        e.preventDefault();

        this._showContextMenu({
            right: e.clientX,
            top: e.clientY,
            height: 0,
        });
    },

    closeMenu: function() {
        this.setState({
            contextMenuPosition: null,
        });
        this.props.refreshSubList();
    },

    render: function() {
        const isInvite = this.props.room.getMyMembership() === "invite";
        const notificationCount = this.props.notificationCount;
        // var highlightCount = this.props.room.getUnreadNotificationCount("highlight");

        const notifBadges = notificationCount > 0 && RoomNotifs.shouldShowNotifBadge(this.state.notifState);
        const mentionBadges = this.props.highlight && RoomNotifs.shouldShowMentionBadge(this.state.notifState);
        const badges = notifBadges || mentionBadges;

        let subtext = null;
        if (this._shouldShowStatusMessage()) {
            subtext = this.state.statusMessage;
        }

        const isMenuDisplayed = Boolean(this.state.contextMenuPosition);

        const dmUserId = DMRoomMap.shared().getUserIdForRoomId(this.props.room.roomId);

        const classes = classNames({
            'mx_RoomTile': true,
            'mx_RoomTile_selected': this.state.selected,
            'mx_RoomTile_unread': this.props.unread,
            'mx_RoomTile_unreadNotify': notifBadges,
            'mx_RoomTile_highlight': mentionBadges,
            'mx_RoomTile_invited': isInvite,
            'mx_RoomTile_menuDisplayed': isMenuDisplayed,
            'mx_RoomTile_noBadges': !badges,
            'mx_RoomTile_transparent': this.props.transparent,
            'mx_RoomTile_hasSubtext': subtext && !this.props.collapsed,
            'mx_RoomTile_isPrivate': this.state.join_rule == "invite" && !dmUserId,
        });

        const avatarClasses = classNames({
            'mx_RoomTile_avatar': true,
        });

        const badgeClasses = classNames({
            'mx_RoomTile_badge': true,
            'mx_RoomTile_badgeButton': this.state.badgeHover || isMenuDisplayed,
        });

        let name = this.state.roomName;
        if (name == undefined || name == null) name = '';
        name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon

        let badge;
        if (badges) {
            const limitedCount = FormattingUtils.formatCount(notificationCount);
            const badgeContent = notificationCount ? limitedCount : '!';
            badge =
{ badgeContent }
;
        }

        let label;
        let subtextLabel;
        let tooltip;
        if (!this.props.collapsed) {
            const nameClasses = classNames({
                'mx_RoomTile_name': true,
                'mx_RoomTile_invite': this.props.isInvite,
                'mx_RoomTile_badgeShown': badges || this.state.badgeHover || isMenuDisplayed,
            });

            subtextLabel = subtext ? { subtext } : null;
            label =
{ name }
;
        } else if (this.state.hover) {
            const Tooltip = sdk.getComponent("elements.Tooltip");
            tooltip = <Tooltip className="mx_RoomTile_tooltip" label={this.props.room.name} dir="auto" />;
        }

        //var incomingCallBox;
        //if (this.props.incomingCall) {
        //    var IncomingCallBox = sdk.getComponent("voip.IncomingCallBox");
        //    incomingCallBox = <IncomingCallBox incomingCall={ this.props.incomingCall }/>;
        //}

        const AccessibleButton = sdk.getComponent('elements.AccessibleButton');

        let contextMenuButton;
        if (!MatrixClientPeg.get().isGuest()) {
            contextMenuButton = (
                <ContextMenuButton
                    className="mx_RoomTile_menuButton"
                    label={_t("Options")}
                    isExpanded={isMenuDisplayed}
                    onClick={this.onContextMenuButtonClick} />
            );
        }

        const RoomAvatar = sdk.getComponent('avatars.RoomAvatar');

        let ariaLabel = name;

        let dmIndicator;
        let dmOnline;
        if (dmUserId) {
            dmIndicator = ;

            const { room } = this.props;
            const member = room.getMember(dmUserId);
            if (member && member.membership === "join" && room.getJoinedMemberCount() === 2) {
                const UserOnlineDot = sdk.getComponent('rooms.UserOnlineDot');
                dmOnline = <UserOnlineDot userId={dmUserId} />;
            }
        }

        // The following labels are written in such a fashion to increase screen reader efficiency (speed).
        if (notifBadges && mentionBadges && !isInvite) {
            ariaLabel += " " + _t("%(count)s unread messages including mentions.", {
                count: notificationCount,
            });
        } else if (notifBadges) {
            ariaLabel += " " + _t("%(count)s unread messages.", { count: notificationCount });
        } else if (mentionBadges && !isInvite) {
            ariaLabel += " " + _t("Unread mentions.");
        } else if (this.props.unread) {
            ariaLabel += " " + _t("Unread messages.");
        }

        let contextMenu;
        if (isMenuDisplayed) {
            const RoomTileContextMenu = sdk.getComponent('context_menus.RoomTileContextMenu');
            contextMenu = (
                <ContextMenu {...toRightOf(this.state.contextMenuPosition)} onFinished={this.closeMenu}>
                    <RoomTileContextMenu room={this.props.room} onFinished={this.closeMenu} />
                </ContextMenu>
            );
        }

        return <React.Fragment>
            <AccessibleButton
                tabIndex="0"
                className={classes}
                onClick={this.onClick}
                onMouseEnter={this.onMouseEnter}
                onMouseLeave={this.onMouseLeave}
                onContextMenu={this.onContextMenu}
                aria-label={ariaLabel}
                aria-selected={this.state.selected}
                role="treeitem"
            >

                        <RoomAvatar room={this.props.room} width={24} height={24} />
                        { dmIndicator }

                        { label }
                        { subtextLabel }

                    { dmOnline }
                    { contextMenuButton }
                    { badge }

                { /* { incomingCallBox } */ }
                { tooltip }
            </AccessibleButton>

            { contextMenu }
        </React.Fragment>;
    },
});
<|endoftext|>


<commit_before>/* Обьект для сжатия скриптов и стилей */

 (function() {
    'use strict';

   if(!global.cloudcmd)
        return console.log(
            '# minify.js'                                       + '\n'  +
            '# -----------'                                     + '\n'  +
            '# Module is part of Cloud Commander,'              + '\n'  +
            '# used for work with minification.'                + '\n'  +
            '# If you wont to see at work set minify'           + '\n'  +
            '# parameters in config.json or environment'        + '\n'  +
            '# and start cloudcmd.js'                           + '\n'  +
            '# http://cloudcmd.io'                              + '\n');

    var main                = global.cloudcmd.main,
        HTMLDIR             = main.HTMLDIR,
        Util                = main.util,
        Minify              = main.require('minify'),
        fs                  = require('fs'),
        IsChanged           = main.ischanged,

        COULD_NOT_MINIFY    = 'Could not minify without minify module\n' +
                            'npm i minify';

    exports.Minify = {
        /* pathes to directories */
        INDEX           : HTMLDIR + 'index.html',

        optimize: function(name, params) {
            var minifyName, isChanged, isExist;

            if (!Minify) {
                Util.log(COULD_NOT_MINIFY);
                Util.exec(params.callback);
            } else {
                minifyName  = Minify.getName(name),

                isChanged   = Util.bind(IsChanged.isFileChanged, name),
                isExist     = Util.bind(fs.exists, minifyName);

                if (!this.MinFolder)
                    this.MinFolder = Minify.MinFolder;

                Util.asyncCall([isChanged, isExist], function(changed, exists) {
                    if (changed || !exists)
                        Minify.optimize(name, params);
                    else
                        Util.exec(params.callback, {
                            name: minifyName
                        });
                });
            }
        },

        /* minification folder name */
        MinFolder   : '',
        getName     : Minify ? Minify.getName : Util.retParam
    };

})();
<commit_msg>fix(minify) callback: params -> error, params<commit_after>/* Обьект для сжатия скриптов и стилей */

 (function() {
    'use strict';

   if(!global.cloudcmd)
        return console.log(
            '# minify.js'                                       + '\n'  +
            '# -----------'                                     + '\n'  +
            '# Module is part of Cloud Commander,'              + '\n'  +
            '# used for work with minification.'                + '\n'  +
            '# If you wont to see at work set minify'           + '\n'  +
            '# parameters in config.json or environment'        + '\n'  +
            '# and start cloudcmd.js'                           + '\n'  +
            '# http://cloudcmd.io'                              + '\n');

    var main                = global.cloudcmd.main,
        HTMLDIR             = main.HTMLDIR,
        Util                = main.util,
        Minify              = main.require('minify'),
        fs                  = require('fs'),
        IsChanged           = main.ischanged,

        COULD_NOT_MINIFY    = 'Could not minify without minify module\n' +
                            'npm i minify';

    exports.Minify = {
        /* pathes to directories */
        INDEX           : HTMLDIR + 'index.html',

        optimize: function(name, params) {
            var minifyName, isChanged, isExist;

            if (!Minify) {
                Util.log(COULD_NOT_MINIFY);
                Util.exec(params.callback);
            } else {
                minifyName  = Minify.getName(name),

                isChanged   = Util.bind(IsChanged.isFileChanged, name),
                isExist     = Util.bind(fs.exists, minifyName);

                if (!this.MinFolder)
                    this.MinFolder = Minify.MinFolder;

                Util.asyncCall([isChanged, isExist], function(changed, exists) {
                    if (changed || !exists)
                        Minify.optimize(name, params);
                    else
                        Util.exec(params.callback, null, {
                            name: minifyName
                        });
                });
            }
        },

        /* minification folder name */
        MinFolder   : '',
        getName     : Minify ? Minify.getName : Util.retParam
    };

})();
<|endoftext|>


<commit_before>"use strict";

// Run jshint as part of normal testing
require('mocha-jshint')();
var assert = require('assert');
var matgrade = require('../lib/matgrade');
var spawn = require('child-process-promise').spawn;
var pipe = require('../');
var BB = require("bluebird");
var path = require('path');
var fs = require('fs-extra-promise');

var clone = function (branch, folder) {
    folder = folder || path.resolve('/tmp/mathoid_' + branch);
    return fs.statAsync(folder)
        .then(function () {
            console.log("removing old clone " + folder);
            return fs.removeAsync(folder);
        })
        .catch(function(e) {})
        .then(function () {
            console.log("Cloning mathoid " + folder);
            return spawn('git', [
                'clone',
                'https://github.com/wikimedia/mathoid',
                '--depth=1',
                '--single-branch',
                '--branch',
                branch,
                folder])
        })
        .then(function () {
            console.log("Installing mathoid " + folder);
            var npm = (process.platform === "win32" ? "npm.cmd" : "npm");
            return spawn(npm, ['i'], {cwd: folder});
        });
};

describe('matgrade', function () {
    before(function () {
        return BB.join(clone('master'), clone('0.6.2'));
        //TODO: Configure mathoid instances
    });
    it('should process a simple file', function () {
        this.timeout(50000);
        var urlA = pipe.config.conf.mathoid_url;
        return pipe.processFile('../test/files/simple-format.json', matgrade.matgradePipe(urlA, urlA));
    });
});
<commit_msg>Disable long running matgrade tests<commit_after>"use strict";

// Run jshint as part of normal testing
require('mocha-jshint')();
var assert = require('assert');
var matgrade = require('../lib/matgrade');
var spawn = require('child-process-promise').spawn;
var pipe = require('../');
var BB = require("bluebird");
var path = require('path');
var fs = require('fs-extra-promise');

var clone = function (branch, folder) {
    folder = folder || path.resolve('/tmp/mathoid_' + branch);
    return fs.statAsync(folder)
        .then(function () {
            console.log("removing old clone " + folder);
            return fs.removeAsync(folder);
        })
        .catch(function(e) {})
        .then(function () {
            console.log("Cloning mathoid " + folder);
            return spawn('git', [
                'clone',
                'https://github.com/wikimedia/mathoid',
                '--depth=1',
                '--single-branch',
                '--branch',
                branch,
                folder])
        })
        .then(function () {
            console.log("Skip Installing mathoid " + folder);
            var npm = (process.platform === "win32" ? "npm.cmd" : "npm");
            // return spawn(npm, ['i'], {cwd: folder});
        });
};

describe('matgrade', function () {
    before(function () {
        return BB.join(clone('master'), clone('0.6.2'));
        //TODO: Configure mathoid instances
    });
    it('should process a simple file', function () {
        this.timeout(50000);
        var urlA = pipe.config.conf.mathoid_url;
        return pipe.processFile('../test/files/simple-format.json', matgrade.matgradePipe(urlA, urlA));
    });
});
<|endoftext|>


<commit_before>ToolStack.Promise = (function(SU,CU){
      var su = SU,
          callbacks = CU,
          isPromise = function isPromise(e){
           //jquery style,check if it has a promise function
           //adding extra check for type of promise and if return type matches objects
           if(su.isObject(e) && "promise" in e){
               //adding a tiny extra bit of check if its a Class promise,which has a signature
               //set to promise string
               if(e.__signature__ && e.__signature__ === "promise") return true;
               //usual checks
               if(e["promise"] && su.isFunction(e["promise"]) && su.isObject(e["promise"]())) return true;
           }

              return false;
      },
      promise = function PromiseCreator(fn){

         var state = "pending",lists = {
            done : callbacks.create("once forceContext"),
            fail : callbacks.create("once forceContext"),
            progress : callbacks.create("forceContext")
         },
         deferred = {},
         memory = [[],[],[]],
         memorydone=memory[0],
         memoryfail=memory[1],
         memorynotify=memory[2],
         handler,
         isRejected = function(){
            if(state === "rejected" && lists.fail.fired()){
               return true;
            }
            return false;
         },

         isResolved = function(){
            if(state === "resolved" && lists.done.fired()){
               return true;
            }
            return false;
         };

         su.extends(deferred, {

               __signature__: "promise",

               __show: function(){ return lists; },

               state : function(){
                  return state;
               },

               done: function(fn){
                  if(!fn) return this;
                  if(isResolved()){
                     su.forEach(su.arranize(arguments),function(e,i){
                        if(!su.isFunction(e)) return;
                        e.apply(memorydone[0],memorydone[1]);
                     });
                     return this;
                  }
                  lists.done.add(fn);
                  return this;
               },

               fail: function(fn){
                  if(!fn) return this;
                  if(isRejected()){
                     su.forEach(su.arranize(arguments),function(e,i){
                        if(!su.isFunction(e)) return;
                        e.apply(memoryfail[0],memoryfail[1]);
                     });
                     return this;
                  }
                  lists.fail.add(fn);
                  return this;
               },

               progress: function(fn){
                  if(!fn) return this;
                  if(isRejected() || isResolved()){
                     su.forEach(su.arranize(arguments),function(e,i){
                        if(!su.isFunction(e)) return;
                        e.apply(memorynotify[0],memorynotify[1]);
                     });
                     return this;
                  }
                  lists.progress.add(fn);
                  return this;
               },

               then: function(done,fail,progress){

                  //returns a new promise which resolves with this promise
                  //adds multiple sets to the current promise/deffered being
                  //called;
                  var self = this,
                      defer = ToolStack.Promise.create();

                  self.then(function(){
                    if(!su.isFunction(done)) return self.done(defer.resolve);
                    var ret = done.apply(self,arguments);
                    if(ret && isPromise(ret)) ret.then(defer.resolve);
                    else defer.resolve(ret);

                  },function(){
                    if(!su.isFunction(fail)) return self.fail(defer.reject);

                  },function(){
                    if(!su.isFunction(progress)) return self.fail(defer.notify);

                  });

                  return defer.promise();
               },

               //return the value used to resolve,reject it
               get: function(){
                  if(isResolved()) return memorydone[1];
                  if(isRejected()) return memoryfail[1];
               },

               resolveWith: function(ctx,args){
                  if(isResolved() || isRejected()){ return this;}
                  //fire necessary callbacks;
                  state = "resolved";
                  lists.done.fireWith(ctx,args);
                  lists.progress.fireWith(ctx,args);
                  //store fired context and arguments for when callbacks are added after resolve/rejection
                  memorydone = [ctx,args];
                  //disable fail list if resolved
                  lists.fail.disable();
                  //set state to resolve
                  return this;
               },

               rejectWith: function(ctx,args){
                  if(isRejected() || isResolved()){ return this; }
                  //fire necessary callbacks;
                  state = "rejected";
                  lists.fail.fireWith(ctx,args);
                  lists.progress.fireWith(ctx,args);
                  //store fired context and arguments for when callbacks are added after resolve/rejection
                  memoryfail = [ctx,args];
                  //disable done/success list;
                  lists.done.disable();
                  //set state to rejected
                  return this;
               },

               notifyWith: function(ctx,args){
                 if(isRejected() || isResolved()) return this;
                 memorynotify = [ctx,args];
                 lists.progress.fireWith(ctx,args);
                 return this;
               },

               notify: function(){
                  var args = su.arranize(arguments);
                  su.bind(function(){
                    this.notifyWith(this,args);
                  },deferred)();
                  return this;
               },

               resolve: function(){
                  var args = su.arranize(arguments);
                  //ensure it keeps the current context
                  su.bind(function(){
                      this.resolveWith(this,args);
                  },deferred)();

                  return this;
               },

               reject: function(){
                  var args = su.arranize(arguments);
                  //ensure it keeps the current context
                  su.bind(function(){
                     this.rejectWith(this,args);
                  },deferred)();

                  return this;
               },

               delay: function(ms){
                 var pros = s.Promise.create();
                 setTimeout(pros.resolve,ms);
                 return pros;
               },

               promise: function(){
                  if(this.__p) return this.__p;

                  var _p = {};
                  su.extends(_p,this);
                  delete _p.resolve;
                  delete _p.reject;
                  delete _p.rejectWith;
                  delete _p.notifyWith;
                  delete _p.notify;

                  // delete _p.promise;
                  _p.promise = function(){ return this; };
                  delete _p.resolveWith;

                  this.__p = _p;
                  return _p;
               }

         });

        if(su.isNull(fn) || su.isFalse(fn)){
           deferred.reject(fn);
           return deferred;
        };

        if(fn){

            //if(su.isTrue(fn)){ deferred.resolve(); return deferred; }

            if(su.isObject(fn) && this.isPromise(fn)){
               handler = fn.promise;
               fn.then(
                  function(){ deferred.resolve(arguments); },
                  function(){ deferred.reject(arguments); },
                  function(){ deferred.notify(arguments); }
               );
               return deferred;
            }

            if(su.isObject(fn) && !this.isPromise(fn)){
               handler = fn;
               deferred.resolve(fn);
               return deferred;
            }

            if(su.isFunction(fn)){
               handler = fn.call(deferred,deferred);
               return deferred;
            }

            if(su.isPrimitive(fn)){
               handler = fn;
               deferred.resolve(fn);
               return deferred;
            }
         }

         return deferred;
      };

      return {
        name: "AppStack.Promises",
         version: "1.0.0",
         description: "Implementation of Promise A spec",
         licenses:[ { type: "mit", url: "http://mths.be/mit" }],
         author: "Alexander Ewetumo",
         create: promise,
         isPromise: isPromise,
         when: function(deffereds){
            //returns a new defferd/promise
            var lists = su.normalizeArray(su.arranize(arguments)),
                self = this,
                count = lists.length,
                procCount = count,
                resValues = new Array(count),
                newDiffered = self.create(),
                promise = newDiffered.promise();

                 su.eachSync(lists,function(e,i,o,fn){
                     var item = ((isPromise(e)) ? e.promise() : self.create(e).promise());
                     if(item){
                        item.then(function(){},function(){
                          newDiffered.reject(arguments);
                        },function(){
                          resValues[i] = arguments.length === 1 ? arguments[0] : su.arranize(arguments);
                        });
                        procCount--;
                        fn(false);
                     }
                 },function(){
                     var cargs = su.flatten(resValues);
                     if(su.isEmpty(cargs)) su.normalizeArray(cargs);
                     cargs = (cargs.length === 0 ? resValues : cargs);
                     if(!procCount) newDiffered.resolveWith(newDiffered,cargs);
                 },this);

            return promise;
         },
      }
})(ToolStack.Utility,ToolStack.Callbacks);
<commit_msg>changing promise.then<commit_after>ToolStack.Promise = (function(SU,CU){
      var su = SU,
          callbacks = CU,
          isPromise = function isPromise(e){
           //jquery style,check if it has a promise function
           //adding extra check for type of promise and if return type matches objects
           if(su.isObject(e) && "promise" in e){
               //adding a tiny extra bit of check if its a Class promise,which has a signature
               //set to promise string
               if(e.__signature__ && e.__signature__ === "promise") return true;
               //usual checks
               if(e["promise"] && su.isFunction(e["promise"]) && su.isObject(e["promise"]())) return true;
           }

              return false;
      },
      promise = function PromiseCreator(fn){

         var state = "pending",lists = {
            done : callbacks.create("once unique forceContext"),
            fail : callbacks.create("once unique forceContext"),
            progress : callbacks.create("unique forceContext")
         },
         deferred = {},
         memory = [[],[],[]],
         memorydone=memory[0],
         memoryfail=memory[1],
         memorynotify=memory[2],
         handler,
         isRejected = function(){
            if(state === "rejected" && lists.fail.fired()){
               return true;
            }
            return false;
         },

         isResolved = function(){
            if(state === "resolved" && lists.done.fired()){
               return true;
            }
            return false;
         };

         su.extends(deferred, {

               __signature__: "promise",

               __show: function(){ return lists; },

               state : function(){
                  return state;
               },

               done: function(fn){
                  if(!fn) return this;
                  if(isResolved()){
                     su.forEach(su.arranize(arguments),function(e,i){
                        if(!su.isFunction(e)) return;
                        e.apply(memorydone[0],memorydone[1]);
                     });
                     return this;
                  }
                  lists.done.add(fn);
                  return this;
               },

               fail: function(fn){
                  if(!fn) return this;
                  if(isRejected()){
                     su.forEach(su.arranize(arguments),function(e,i){
                        if(!su.isFunction(e)) return;
                        e.apply(memoryfail[0],memoryfail[1]);
                     });
                     return this;
                  }
                  lists.fail.add(fn);
                  return this;
               },

               progress: function(fn){
                  if(!fn) return this;
                  if(isRejected() || isResolved()){
                     su.forEach(su.arranize(arguments),function(e,i){
                        if(!su.isFunction(e)) return;
                        e.apply(memorynotify[0],memorynotify[1]);
                     });
                     return this;
                  }
                  lists.progress.add(fn);
                  return this;
               },

               then: function(done,fail,progress){

                  //returns a new promise which resolves with this promise
                  //adds multiple sets to the current promise/deffered being
                  //called;

                  var self = this,
                      defer = ToolStack.Promise.create(),
                      isp = false,
                      stateHandler = function stateHandler(action,state,fn){
                        return function(){
                          if(!su.isFunction(fn)) return self[action](defer[state]);
                          else{
                            var returned = fn.apply(this,arguments);
                            if(returned && isPromise(returned) && !isp){
                              returned.promise().then(defer.resolve,defer.reject,defer.notify);
                              isp = true;
                              return;
                            }
                            else{
                              defer[state+"With"](defer,[returned]);
                            }
                          }
                        }
                      };

                  self.done(stateHandler('done','resolve',done))
                  .fail(stateHandler('fail','reject',fail))
                  .progress(stateHandler('progress','notify',progress))
                  //add the call back

                  return defer.promise();
               },

               //return the value used to resolve,reject it
               get: function(){
                  if(isResolved()) return memorydone[1];
                  if(isRejected()) return memoryfail[1];
               },

               resolveWith: function(ctx,args){
                  if(isResolved() || isRejected()){ return this;}
                  //fire necessary callbacks;
                  state = "resolved";
                  lists.done.fireWith(ctx,args);
                  lists.progress.fireWith(ctx,args);
                  //store fired context and arguments for when callbacks are added after resolve/rejection
                  memorydone = [ctx,args];
                  //disable fail list if resolved
                  lists.fail.disable();
                  //set state to resolve
                  return this;
               },

               rejectWith: function(ctx,args){
                  if(isRejected() || isResolved()){ return this; }
                  //fire necessary callbacks;
                  state = "rejected";
                  lists.fail.fireWith(ctx,args);
                  lists.progress.fireWith(ctx,args);
                  //store fired context and arguments for when callbacks are added after resolve/rejection
                  memoryfail = [ctx,args];
                  //disable done/success list;
                  lists.done.disable();
                  //set state to rejected
                  return this;
               },

               notifyWith: function(ctx,args){
                 if(isRejected() || isResolved()) return this;
                 memorynotify = [ctx,args];
                 lists.progress.fireWith(ctx,args);
                 return this;
               },

               notify: function(){
                  var args = su.arranize(arguments);
                  su.bind(function(){
                    this.notifyWith(this,args);
                  },deferred)();
               },

               resolve: function(){
                  var args = su.arranize(arguments);
                  //ensure it keeps the current context
                  su.bind(function(){
                      this.resolveWith(this,args);
                  },deferred)();

               },

               reject: function(){
                  var args = su.arranize(arguments);
                  //ensure it keeps the current context
                  su.bind(function(){
                     this.rejectWith(this,args);
                  },deferred)();

               },

               delay: function(ms){
                 var pros = s.Promise.create();
                 setTimeout(pros.resolve,ms);
                 return pros;
               },

               promise: function(){
                  if(this.__p) return this.__p;

                  var _p = {};
                  su.extends(_p,this);
                  delete _p.resolve;
                  delete _p.reject;
                  delete _p.rejectWith;
                  delete _p.notifyWith;
                  delete _p.notify;

                  // delete _p.promise;
                  _p.promise = function(){ return this; };
                  delete _p.resolveWith;

                  this.__p = _p;
                  return _p;
               }

         });

        if(su.isNull(fn) || su.isFalse(fn)){
           deferred.reject(fn);
           return deferred;
        };

        if(fn){

            //if(su.isTrue(fn)){ deferred.resolve(); return deferred; }

            if(su.isObject(fn) && this.isPromise(fn)){
               handler = fn.promise;
               fn.then(
                  function(){ deferred.resolve(arguments); },
                  function(){ deferred.reject(arguments); },
                  function(){ deferred.notify(arguments); }
               );
               return deferred;
            }

            if(su.isObject(fn) && !this.isPromise(fn)){
               handler = fn;
               deferred.resolve(fn);
               return deferred;
            }

            if(su.isFunction(fn)){
               handler = fn.call(deferred,deferred);
               return deferred;
            }

            if(su.isPrimitive(fn)){
               handler = fn;
               deferred.resolve(fn);
               return deferred;
            }
         }

         return deferred;
      };

      return {
        name: "AppStack.Promises",
         version: "1.0.0",
         description: "Implementation of Promise A spec",
         licenses:[ { type: "mit", url: "http://mths.be/mit" }],
         author: "Alexander Ewetumo",
         create: promise,
         isPromise: isPromise,
         when: function(deffereds){
            //returns a new defferd/promise
            var lists = su.normalizeArray(su.arranize(arguments)),
                self = this,
                count = lists.length,
                procCount = count,
                resValues = new Array(count),
                newDiffered = self.create(),
                promise = newDiffered.promise();

                 su.eachSync(lists,function(e,i,o,fn){
                     var item = ((isPromise(e)) ? e.promise() : self.create(e).promise());
                     if(item){
                        item.then(function(){},function(){
                          newDiffered.reject(arguments);
                        },function(){
                          resValues[i] = arguments.length === 1 ? arguments[0] : su.arranize(arguments);
                        });
                        procCount--;
                        fn(false);
                     }
                 },function(){
                     var cargs = su.flatten(resValues);
                     if(su.isEmpty(cargs)) su.normalizeArray(cargs);
                     cargs = (cargs.length === 0 ? resValues : cargs);
                     if(!procCount) newDiffered.resolveWith(newDiffered,cargs);
                 },this);

            return promise;
         },
      }
})(ToolStack.Utility,ToolStack.Callbacks);
<|endoftext|>


<commit_before>var ordering = require('../ordering.js');

var listEqual = function(l1, l2, test) {
	test.equal(l1.length, l2.length);
	l1.forEach(function(element, index) {
		test.equal(element, l2[index]);
	});
}

exports['test'] = {
	'reverse': function(test) {
		var list = [2, 3, 0].sort(ordering.reverse());
		listEqual(list, [3, 2, 0], test);
		test.done();
	},
	'double reverse': function(test) {
		var list = [2, 3, 0].sort(ordering.reverse().reverse());
		listEqual(list, [0, 2, 3], test);
		test.done();
	},
	'on result of': function(test) {
		var v1 = {
			value: 1
		}, v3 = {
				value: 3
			}, v5 = {
				value: 5
			},
			list = [v3, v5, v1].sort(ordering.onResultOf(function(item) {
				return item.value;
			}));
		listEqual(list, [v1, v3, v5], test);
		// other syntax
		list = [v3, v5, v1].sort(ordering.onResultOf(function(item) {
			return this.value;
		}));
		listEqual(list, [v1, v3, v5], test);
		test.done();
	},
	'all': function(test) {
		var v11 = {
			value: 1,
			otherValue: 1
		}, v12 = {
				value: 1,
				otherValue: 2
			}, v21 = {
				value: 2,
				otherValue: 1
			}, v13 = {
				value: 1,
				otherValue: 3
			}, v22 = {
				value: 2,
				otherValue: 2
			},
			list = [v22, v13, v12, v21, v11].sort(ordering.onResultOf(function(item) {
				return item.value;
			}).coumpound(ordering.onResultOf(function() {
				return this.otherValue;
			}).reverse()));

		listEqual(list, [v13, v12, v11, v22, v21], test);
		test.done();

	}
};<commit_msg>add more tests<commit_after>var ordering = require('../ordering.js');

var listEqual = function(l1, l2, test) {
	test.equal(l1.length, l2.length);
	l1.forEach(function(element, index) {
		test.equal(element, l2[index]);
	});
}

exports['test'] = {
	'reverse': function(test) {
		var list = [2, 3, 0].sort(ordering.reverse());
		listEqual(list, [3, 2, 0], test);
		test.done();
	},
	'double reverse': function(test) {
		var list = [2, 3, 0].sort(ordering.reverse().reverse());
		listEqual(list, [0, 2, 3], test);
		test.done();
	},
	'on result of': function(test) {
		var v1 = {
			value: 1
		}, v3 = {
				value: 3
			}, v5 = {
				value: 5
			},
			list = [v3, v5, v1].sort(ordering.onResultOf(function(item) {
				return item.value;
			}));
		listEqual(list, [v1, v3, v5], test);
		// other syntax
		list = [v3, v5, v1].sort(ordering.onResultOf(function() {
			return this.value;
		}));
		listEqual(list, [v1, v3, v5], test);
		test.done();
	},
	'reverse then on result of': function(test) {
		var v1 = {
			value: 1
		}, v3 = {
				value: 3
			}, v5 = {
				value: 5
			},
			list = [v3, v5, v1].sort(ordering.reverse().onResultOf(function() {
				return this.value;
			}));
		listEqual(list, [v5, v3, v1], test);
		test.done();
	},
	'all': function(test) {
		var v11 = {
			value: 1,
			otherValue: 1
		}, v12 = {
				value: 1,
				otherValue: 2
			}, v21 = {
				value: 2,
				otherValue: 1
			}, v13 = {
				value: 1,
				otherValue: 3
			}, v22 = {
				value: 2,
				otherValue: 2
			},
			list = [v22, v13, v12, v21, v11].sort(ordering.onResultOf(function(item) {
				return item.value;
			}).coumpound(ordering.onResultOf(function() {
				return this.otherValue;
			}).reverse()));

		listEqual(list, [v13, v12, v11, v22, v21], test);
		test.done();

	}
};<|endoftext|>


<commit_before>#! /usr/bin/env node

/*
 * steam-scraper
 * https://github.com/jeffminnear/steam-scraper
 *
 * Copyright (c) 2016 Jeff Minnear
 * Licensed under the MIT license.
 */

'use strict';

var program = require('commander');
var request = require('request');
var cheerio = require('cheerio');
const chalk = require('chalk');
var colorLegend = "Prices in " + chalk.green("green") + " are current sale prices, shown next to their normal price in " + chalk.gray("gray") + ".";
var steamSearchUrl = "http://store.steampowered.com/search/?snr=1_7_7_151_12&term=";

program
    .version('0.0.1')
    .option('-t, --title <string>', 'the title of the game you want to price-check', String)
    .option('-l, --limit <n>', 'the number of results you want to see (default is 1)', Number)
    .parse(process.argv);

var formattedPrice = function formattedPrice(price) {
    price.trim();
    var prices = price.split("$");
    prices.shift();

    return prices;
};

var gameSearchUrl = steamSearchUrl + encodeURIComponent(program.title);
console.log(colorLegend);

request(gameSearchUrl, function(error, response, html){
    if (!error) {
        var $ = cheerio.load(html);

        var title, price;

        var i = 0;

        while (i <= program.limit - 1) {

            $('a div div span.title').eq(i).filter(function(){
                var data = $(this);

                title = data.text();
            });

            $('.search_price.responsive_secondrow').eq(i).filter(function(){
                var data = $(this);

                price = data.text();

                var prices = formattedPrice(price);

                if (prices.length > 1) {
                    price = chalk.gray("$" + prices[0]) + "  " + chalk.green(prices[1]);
                } else {
                    price = "$" + prices[0];
                }
            });

            console.log(title + ": " + price);

            i++;
        }
    }
});
<commit_msg>Modify steam-scraper.js header information<commit_after>#! /usr/bin/env node

/*
 * steam-scraper
 * Copyright (c) 2016 Jeff Minnear
 * Licensed under the MIT license.
 */

'use strict';

var program = require('commander');
var request = require('request');
var cheerio = require('cheerio');
const chalk = require('chalk');
var colorLegend = "Prices in " + chalk.green("green") + " are current sale prices, shown next to their normal price in " + chalk.gray("gray") + ".";
var steamSearchUrl = "http://store.steampowered.com/search/?snr=1_7_7_151_12&term=";

program
    .version('0.0.1')
    .option('-t, --title <string>', 'the title of the game you want to price-check', String)
    .option('-l, --limit <n>', 'the number of results you want to see (default is 1)', Number)
    .parse(process.argv);

var formattedPrice = function formattedPrice(price) {
    price.trim();
    var prices = price.split("$");
    prices.shift();

    return prices;
};

var gameSearchUrl = steamSearchUrl + encodeURIComponent(program.title);
console.log(colorLegend);

request(gameSearchUrl, function(error, response, html){
    if (!error) {
        var $ = cheerio.load(html);

        var title, price;

        var i = 0;

        while (i <= program.limit - 1) {

            $('a div div span.title').eq(i).filter(function(){
                var data = $(this);

                title = data.text();
            });

            $('.search_price.responsive_secondrow').eq(i).filter(function(){
                var data = $(this);

                price = data.text();

                var prices = formattedPrice(price);

                if (prices.length > 1) {
                    price = chalk.gray("$" + prices[0]) + "  " + chalk.green(prices[1]);
                } else {
                    price = "$" + prices[0];
                }
            });

            console.log(title + ": " + price);

            i++;
        }
    }
});
<|endoftext|>


<commit_before>var _ = require("lodash"),
    uuid = require('node-uuid');

exports.create = function (appLog) {
    function StubRegistry(appLog) {

        var registry = [],
            log = appLog.child({ component: "StubRegistry" });

        function validateForRegistry(stub) {
            if (!stub.criteria) {
                throw new Error("criteria must be specified.");
            }

            if (!stub.criteria.url) {
                throw new Error("url must be specified in the criteria.");
            }

            if (stub.criteria.body && !stub.criteria.contentType) {
                throw new Error("the contentType for a criteria body must be specified.");
            }

            if (!stub.respondWith) {
                throw new Error("respondWith must be specified.");
            }

            if (!stub.respondWith.status) {
                throw new Error("status must be specified in the respondWith.");
            }

            if (stub.respondWith.body && !stub.respondWith.contentType) {
                throw new Error("the respondWith contentType for a body must be specified.");
            }

            if (_.isString(stub.criteria.url) && (stub.criteria.url.substr(0, 1) !== "/")) {
                stub.criteria.url = "/" + stub.criteria.url;
            }
        }

        function initialiseStub(stub) {
            function assignContentTypeOn(object) {
                if (object.headers && object.headers["Content-Type"]) {
                    object.contentType = object.headers["Content-Type"];
                }
            }

            if (!stub.criteria.method) {
                stub.criteria.method = "GET";
            }

            assignContentTypeOn(stub.criteria);
            assignContentTypeOn(stub.respondWith);

            stub.matched = 0;
            stub.id = uuid.v4();
        }

        function asString(stub) {
            return stub.criteria.method + " " + stub.criteria.url;
        }

        this.addStub = function (stub) {
            validateForRegistry(stub);

            if (stub.times !== undefined) {
                stub.expect = true;
            }

            initialiseStub(stub);

            registry.push(stub);

            return stub;
        };

        this.getById = function (stubId) {
            var matches = registry.filter(function (stub) {
                return stub.id === stubId;
            });

            if (matches.length > 0) {
                return matches[0];
            } else {
                return null;
            }
        };

        this.clear = function () {
            registry.length = 0;
            log.debug("Registry cleared.");
        };

        this.verifyExpectations = function () {
            var errors = [];

            registry.filter(
                function (stub) {
                    return stub.expect;
                }).forEach(function (expectation) {
                    if (expectation.times !== expectation.matched) {
                        var message = asString(expectation) + " - Expected: " + expectation.times + "; Actual: " + expectation.matched;
                        log.debug("VERIFICATION FAILED: " + message);
                        errors.push(message);
                    }
                });

            return errors;
        };

        this.findMatchFor = function (request) {
            var requestUrl = ((request.url.substr(0, 1) !== "/") ? "/" : "") + request.url;

            log.debug("Finding match for request: " + requestUrl);

            var matchingStubs = registry.filter(function (stub) {
                if (!requestUrl.match(stub.criteria.url)) {
                    log.debug("REJECTED on url [Expected: " + stub.criteria.url + "; Actual: " + requestUrl + "]");
                    return false;
                }

                if (stub.criteria.method && (request.method.toLowerCase() !== stub.criteria.method.toLowerCase())) {
                    log.debug("REJECTED on method [Expected: " + stub.criteria.method + "; Actual: " + request.method + "]");
                    return false;
                }

                if (stub.criteria.headers) {
                    var rejectedHeader = _.keys(stub.criteria.headers).filter(function (headerName) {
                        var headerValue = request.headers[headerName.toLowerCase()];
                        return !headerValue || !headerValue.match(stub.criteria.headers[headerName]);
                    });

                    if (rejectedHeader.length > 0) {
                        var headerName = rejectedHeader[0];
                        log.debug("REJECTED on header '" + headerName + "' [Expected: '" +
                            stub.criteria.headers[headerName] + "'; Actual: '" + request.headers[headerName] + "']");
                        return false;
                    }
                }

                if (stub.criteria.body) {
                    if (_.isFunction(stub.criteria.body)) {
                        if (!stub.criteria.body(request.body)) {
                            log.debug("REJECTED on body [Expected: body matched by function; Actual: " + request.body + "]");
                            return false;
                        }

                    } else if (!JSON.stringify(request.body).match(stub.criteria.body)) {
                        log.debug("REJECTED on body [Expected: '" + stub.criteria.body + "'; Actual: " + JSON.stringify(request.body) + "]");
                        return false;

                    } else if (stub.criteria.contentType !== request.contentType()) {
                        log.debug("REJECTED on body content type [Expected: '" + stub.criteria.contentType + "'; Actual: " + request.contentType() + "]");
                        return false;
                    }
                }

                if (!stub.expect && (stub.matched === stub.times)) {
                    log.debug("REJECTED on stub has already been matched " + stub.matched + " times.");
                    return false;
                }

                stub.matched++;

                log.debug("MATCHED: " + JSON.stringify(stub));

                return true;
            });

            if (matchingStubs.length === 1) {
                return matchingStubs[0];

            } else if (matchingStubs.length > 1) {
                throw new Error("More than one stub matched the request.");

            } else {
                return null;
            }
        };

        this.clear();
    }

    return new StubRegistry(appLog);
};<commit_msg>Log tweaking.<commit_after>var _ = require("lodash"),
    uuid = require('node-uuid');

exports.create = function (appLog) {
    function StubRegistry(appLog) {

        var registry = [],
            log = appLog.child({ component: "StubRegistry" });

        function validateForRegistry(stub) {
            if (!stub.criteria) {
                throw new Error("criteria must be specified.");
            }

            if (!stub.criteria.url) {
                throw new Error("url must be specified in the criteria.");
            }

            if (stub.criteria.body && !stub.criteria.contentType) {
                throw new Error("the contentType for a criteria body must be specified.");
            }

            if (!stub.respondWith) {
                throw new Error("respondWith must be specified.");
            }

            if (!stub.respondWith.status) {
                throw new Error("status must be specified in the respondWith.");
            }

            if (stub.respondWith.body && !stub.respondWith.contentType) {
                throw new Error("the respondWith contentType for a body must be specified.");
            }

            if (_.isString(stub.criteria.url) && (stub.criteria.url.substr(0, 1) !== "/")) {
                stub.criteria.url = "/" + stub.criteria.url;
            }
        }

        function initialiseStub(stub) {
            function assignContentTypeOn(object) {
                if (object.headers && object.headers["Content-Type"]) {
                    object.contentType = object.headers["Content-Type"];
                }
            }

            if (!stub.criteria.method) {
                stub.criteria.method = "GET";
            }

            assignContentTypeOn(stub.criteria);
            assignContentTypeOn(stub.respondWith);

            stub.matched = 0;
            stub.id = uuid.v4();
        }

        function asString(request) {
            return request.method + " " + request.url;
        }

        this.addStub = function (stub) {
            validateForRegistry(stub);

            if (stub.times !== undefined) {
                stub.expect = true;
            }

            initialiseStub(stub);

            registry.push(stub);

            return stub;
        };

        this.getById = function (stubId) {
            var matches = registry.filter(function (stub) {
                return stub.id === stubId;
            });

            if (matches.length > 0) {
                return matches[0];
            } else {
                return null;
            }
        };

        this.clear = function () {
            registry.length = 0;
            log.debug("Registry cleared.");
        };

        this.verifyExpectations = function () {
            var errors = [];

            registry.filter(
                function (stub) {
                    return stub.expect;
                }).forEach(function (expectation) {
                    if (expectation.times !== expectation.matched) {
                        var message = asString(expectation.criteria) + " - Expected: " + expectation.times + "; Actual: " + expectation.matched;
                        log.debug("VERIFICATION FAILED: " + message);
                        errors.push(message);
                    }
                });

            return errors;
        };

        this.findMatchFor = function (request) {
            var requestUrl = ((request.url.substr(0, 1) !== "/") ? "/" : "") + request.url,
                match;

            log.debug("REQUEST: " + request.method + " " + requestUrl);

            var matchingStubs = registry.filter(function (stub) {
                if (!requestUrl.match(stub.criteria.url)) {
                    log.debug("REJECTED stub '" + stub.id + "' on url [Expected: " + stub.criteria.url + "; Actual: " + requestUrl + "]");
                    return false;
                }

                if (stub.criteria.method && (request.method.toLowerCase() !== stub.criteria.method.toLowerCase())) {
                    log.debug("REJECTED stub '" + stub.id + "' on method [Expected: " + stub.criteria.method + "; Actual: " + request.method + "]");
                    return false;
                }

                if (stub.criteria.headers) {
                    var rejectedHeader = _.keys(stub.criteria.headers).filter(function (headerName) {
                        var headerValue = request.headers[headerName.toLowerCase()];
                        return !headerValue || !headerValue.match(stub.criteria.headers[headerName]);
                    });

                    if (rejectedHeader.length > 0) {
                        var headerName = rejectedHeader[0];
                        log.debug("REJECTED stub '" + stub.id + "' on header '" + headerName + "' [Expected: '" +
                            stub.criteria.headers[headerName] + "'; Actual: '" + request.headers[headerName] + "']");
                        return false;
                    }
                }

                if (stub.criteria.body) {
                    if (_.isFunction(stub.criteria.body)) {
                        if (!stub.criteria.body(request.body)) {
                            log.debug("REJECTED stub '" + stub.id + "' on body [Expected: body matched by function; Actual: " + request.body + "]");
                            return false;
                        }

                    } else if (!JSON.stringify(request.body).match(stub.criteria.body)) {
                        log.debug("REJECTED stub '" + stub.id + "' on body [Expected: '" + stub.criteria.body + "'; Actual: " + JSON.stringify(request.body) + "]");
                        return false;

                    } else if (stub.criteria.contentType !== request.contentType()) {
                        log.debug("REJECTED stub '" + stub.id + "' on body content type [Expected: '" + stub.criteria.contentType + "'; Actual: " + request.contentType() + "]");
                        return false;
                    }
                }

                if (!stub.expect && (stub.matched === stub.times)) {
                    log.debug("REJECTED stub '" + stub.id + "' on stub has already been matched " + stub.matched + " times.");
                    return false;
                }

                return true;
            });

            if (matchingStubs.length === 1) {
                match = matchingStubs[0];
                log.debug("MATCHED request " + asString(request) + " against stub: " + JSON.stringify(match));

                match.matched++;

                return match;

            } else if (matchingStubs.length > 1) {
                throw new Error("More than one stub matched the request.");

            } else {
                return null;
            }
        };

        this.clear();
    }

    return new StubRegistry(appLog);
};<|endoftext|>


<commit_before>const { v4: uuidv4 } = require('uuid');
const fs = require('fs');

class TestSteps {

  constructor() {
    this.cb = this.callback;
    this.finish();
  }

  get currentSuite() {
    return this.suites.length > 0 ? this.suites[this.suites.length - 1] : null;
  }

  get currentTest() {
    const currentSuite = this.currentSuite;
    if ( !currentSuite || currentSuite.tests.length === 0 )
      return null;
    return currentSuite.tests[currentSuite.tests.length - 1];
  }

  async finish() {
    if ( this.suites )
      for ( const suite of this.suites ) {
        await this._onSuiteFinish(suite);
      }

    this.suites = [];
    this._before = null;
    this._beforeEach = null;
    this._after = null;
    this._afterEach = null;
  }

  async describe(suiteName, fn) {
    const suite = this._createSuite(this._getSuitePrefix() + suiteName, false);
    fn();
    await this._executeSuite(suite);
  }

  async it(testName, fn) {
    let suite = this.currentSuite;
    if ( !suite ) {
      suite = this._createSuite('Default Suite', true);
      await this._onSuiteStart(suite);
    }
    let test = this._createTest(suite, testName, fn);
    if ( suite.executeTestsImmediately ) {
      await this._executeTest(suite, test);
      await this._onSuiteFinish(suite);
    } else
      suite.tests.push(test);
  }

  before(fn) {
    this._register('_before', fn);
  }

  beforeEach(fn) {
    this._register('_beforeEach', fn);
  }

  after(fn) {
    this._register('_after', fn);
  }

  afterEach(fn) {
    this._register('_afterEach', fn);
  }

  _getSuitePrefix() {
    if ( this.suites.length === 0 )
      return '';
    let answer = '';
    for ( const suite of this.suites )
      answer += `${suite.name} => `;
    return answer;
  }

  _register(hook, fn) {
    const suite = this.currentSuite;
    if ( suite )
      suite[hook] = fn;
    else
      this[hook] = fn;
  }

  _createSuite(suiteName, isDefault) {
    const suite = {
      uuid: uuidv4(),
      name: suiteName,
      tests: [],
      started: Date.now(),
      hasError: false,
      executeTestsImmediately: isDefault
    };
    this.suites.push(suite);
    return suite;
  }

  _createTest(suite, name, fn) {
    return {
      suiteUuid: suite.uuid,
      suiteName: suite.name,
      uuid: uuidv4(),
      name: name,
      fn: fn,
      started: Date.now()
    };
  }

  async _executeSuite(suite) {
    await this._onSuiteStart(suite);

    if ( suite.tests )
      for ( const test of suite.tests ) {
        await this._executeTest(suite, test);
      }

    await this._onSuiteFinish(suite);
  }

  async _onSuiteStart(suite) {
    const before = suite._before || this._before;
    if ( before )
      await this._executeFn(before);

    if ( this.cb.startSuite )
      this.cb.startSuite(suite);
  }

  async _onSuiteFinish(suite) {
    suite.finished = Date.now();
    suite.duration = Date.now() - suite.started;

    const after = suite._after || this._after;
    if ( after )
      await this._executeFn(after);

    this.suites.pop();
    if ( this.cb.finishSuite )
      this.cb.finishSuite(suite);
  }

  async _executeTest(suite, test) {
    const beforeEach = suite._beforeEach || this._beforeEach;
    if ( beforeEach )
      await this._executeFn(beforeEach);

    await this._executeStep(test.fn, suite, test);

    const afterEach = suite._afterEach || this._afterEach;
    if ( afterEach )
      await this._executeFn(afterEach);
  }

  async _executeStep(fn, suite, test) {
    if ( this.cb.startTest )
      this.cb.startTest(test, suite);

    try {
      await this._executeFn(fn);
      test.state = 'passed';
    } catch (err) {
      suite.hasError = true;
      test.state = 'failed';
      test.errorType = err.name;
      if ( err.message )
        test.error = err.message;
      if ( err.stack )
        test.errorTrace = this.toErrorContent(err);
    }
    test.finished = Date.now();
    test.duration = Date.now() - test.started;

    if ( this.cb.finishTest )
      this.cb.finishTest(test, suite);
  }

  async _executeFn(fn) {
    const isAsync = fn.constructor.name === 'AsyncFunction';
    if ( isAsync ) {
      await fn();
    } else if ( fn.length === 1 ) {
      await new Promise((resolve, reject) => {
        fn((err) => {
          if ( err )
            reject(new Error(err));
          else
            resolve();
        });
      });
    } else {
      fn();
    }
  }

  callback = {
    startSuite: (suite) => {
      this.reportTestSuiteActions([{ type: 'StartSuite', data: suite }]);
    },
    finishSuite: (suite) => {
      this.reportTestSuiteActions([{ type: 'FinishSuite', data: suite }]);
    },
    startTest: (test) => {
      this.reportTestSuiteActions([{ type: 'StartSuiteTest', data: test }]);
    },
    finishTest: (test) => {
      this.reportTestSuiteActions([{ type: 'FinishSuiteTest', data: test }]);
    }
  }

  reportTestSuiteActions(suiteActions) {
    this.sendMsg({
      type: "TestSuiteActions",
      data: suiteActions
    });
  }

  sendMsg(result) {
    console.log('---------: sendMsg')
    console.log(JSON.stringify(result));
    console.log(process.env.TESTABLE_RESULT_FILE);
    return new Promise((resolve, reject) => {
      if ( process.env.TESTABLE_RESULT_FILE ) {
        fs.writeFile(process.env.TESTABLE_RESULT_FILE, JSON.stringify(result) + '\n', {
          encoding: 'utf8',
          flag: 'a'
        }, resolve);
      } else {
        console.log('[Result] ' + JSON.stringify(result, null, '\t'));
        resolve();
      }
    });
  }
}

module.exports = TestSteps;
<commit_msg>console removed<commit_after>const { v4: uuidv4 } = require('uuid');
const fs = require('fs');

class TestSteps {

  constructor() {
    this.cb = this.callback;
    this.finish();
  }

  get currentSuite() {
    return this.suites.length > 0 ? this.suites[this.suites.length - 1] : null;
  }

  get currentTest() {
    const currentSuite = this.currentSuite;
    if ( !currentSuite || currentSuite.tests.length === 0 )
      return null;
    return currentSuite.tests[currentSuite.tests.length - 1];
  }

  async finish() {
    if ( this.suites )
      for ( const suite of this.suites ) {
        await this._onSuiteFinish(suite);
      }

    this.suites = [];
    this._before = null;
    this._beforeEach = null;
    this._after = null;
    this._afterEach = null;
  }

  async describe(suiteName, fn) {
    const suite = this._createSuite(this._getSuitePrefix() + suiteName, false);
    fn();
    await this._executeSuite(suite);
  }

  async it(testName, fn) {
    let suite = this.currentSuite;
    if ( !suite ) {
      suite = this._createSuite('Default Suite', true);
      await this._onSuiteStart(suite);
    }
    let test = this._createTest(suite, testName, fn);
    if ( suite.executeTestsImmediately ) {
      await this._executeTest(suite, test);
      await this._onSuiteFinish(suite);
    } else
      suite.tests.push(test);
  }

  before(fn) {
    this._register('_before', fn);
  }

  beforeEach(fn) {
    this._register('_beforeEach', fn);
  }

  after(fn) {
    this._register('_after', fn);
  }

  afterEach(fn) {
    this._register('_afterEach', fn);
  }

  _getSuitePrefix() {
    if ( this.suites.length === 0 )
      return '';
    let answer = '';
    for ( const suite of this.suites )
      answer += `${suite.name} => `;
    return answer;
  }

  _register(hook, fn) {
    const suite = this.currentSuite;
    if ( suite )
      suite[hook] = fn;
    else
      this[hook] = fn;
  }

  _createSuite(suiteName, isDefault) {
    const suite = {
      uuid: uuidv4(),
      name: suiteName,
      tests: [],
      started: Date.now(),
      hasError: false,
      executeTestsImmediately: isDefault
    };
    this.suites.push(suite);
    return suite;
  }

  _createTest(suite, name, fn) {
    return {
      suiteUuid: suite.uuid,
      suiteName: suite.name,
      uuid: uuidv4(),
      name: name,
      fn: fn,
      started: Date.now()
    };
  }

  async _executeSuite(suite) {
    await this._onSuiteStart(suite);

    if ( suite.tests )
      for ( const test of suite.tests ) {
        await this._executeTest(suite, test);
      }

    await this._onSuiteFinish(suite);
  }

  async _onSuiteStart(suite) {
    const before = suite._before || this._before;
    if ( before )
      await this._executeFn(before);

    if ( this.cb.startSuite )
      this.cb.startSuite(suite);
  }

  async _onSuiteFinish(suite) {
    suite.finished = Date.now();
    suite.duration = Date.now() - suite.started;

    const after = suite._after || this._after;
    if ( after )
      await this._executeFn(after);

    this.suites.pop();
    if ( this.cb.finishSuite )
      this.cb.finishSuite(suite);
  }

  async _executeTest(suite, test) {
    const beforeEach = suite._beforeEach || this._beforeEach;
    if ( beforeEach )
      await this._executeFn(beforeEach);

    await this._executeStep(test.fn, suite, test);

    const afterEach = suite._afterEach || this._afterEach;
    if ( afterEach )
      await this._executeFn(afterEach);
  }

  async _executeStep(fn, suite, test) {
    if ( this.cb.startTest )
      this.cb.startTest(test, suite);

    try {
      await this._executeFn(fn);
      test.state = 'passed';
    } catch (err) {
      suite.hasError = true;
      test.state = 'failed';
      test.errorType = err.name;
      if ( err.message )
        test.error = err.message;
      if ( err.stack )
        test.errorTrace = this.toErrorContent(err);
    }
    test.finished = Date.now();
    test.duration = Date.now() - test.started;

    if ( this.cb.finishTest )
      this.cb.finishTest(test, suite);
  }

  async _executeFn(fn) {
    const isAsync = fn.constructor.name === 'AsyncFunction';
    if ( isAsync ) {
      await fn();
    } else if ( fn.length === 1 ) {
      await new Promise((resolve, reject) => {
        fn((err) => {
          if ( err )
            reject(new Error(err));
          else
            resolve();
        });
      });
    } else {
      fn();
    }
  }

  callback = {
    startSuite: (suite) => {
      this.reportTestSuiteActions([{ type: 'StartSuite', data: suite }]);
    },
    finishSuite: (suite) => {
      this.reportTestSuiteActions([{ type: 'FinishSuite', data: suite }]);
    },
    startTest: (test) => {
      this.reportTestSuiteActions([{ type: 'StartSuiteTest', data: test }]);
    },
    finishTest: (test) => {
      this.reportTestSuiteActions([{ type: 'FinishSuiteTest', data: test }]);
    }
  }

  reportTestSuiteActions(suiteActions) {
    this.sendMsg({
      type: "TestSuiteActions",
      data: suiteActions
    });
  }

  sendMsg(result) {
    return new Promise((resolve, reject) => {
      if ( process.env.TESTABLE_RESULT_FILE ) {
        fs.writeFile(process.env.TESTABLE_RESULT_FILE, JSON.stringify(result) + '\n', {
          encoding: 'utf8',
          flag: 'a'
        }, resolve);
      } else {
        console.log('[Result] ' + JSON.stringify(result, null, '\t'));
        resolve();
      }
    });
  }
}

module.exports = TestSteps;
<|endoftext|>