<commit_before>/// <reference types="cypress" />

function removeExtraSpaces(text) {
  return `${text}`.replace(/\s+/g, ' ').trim();
}

describe('Example 27 - Tree Data (from a Hierarchical Dataset)', { retries: 1 }, () => {
  const GRID_ROW_HEIGHT = 45;
  const titles = ['Title', 'Duration', '% Complete', 'Start', 'Finish', 'Effort Driven'];

  it('should display Example title', () => {
    cy.visit(`${Cypress.config('baseExampleUrl')}/example27`);
    cy.get('h2').should('contain', 'Example 27: Tree Data (from a flat dataset with parentId references)');
  });

  it('should have exact column titles in grid', () => {
    cy.get('#grid27')
      .find('.slick-header-columns')
      .children()
      .each(($child, index) => expect($child.text()).to.eq(titles[index]));
  });

  it('should have a Grid Preset Filter on 3rd column "% Complete" and expect all rows to be filtered as well', () => {
    cy.get('.input-group-text.rangeOutput_percentComplete')
      .contains('25');

    cy.get('.search-filter.filter-percentComplete')
      .find('.input-group-addon.operator select')
      .contains('>=');
  });

  it('should collapsed all rows from "Collapse All" button', () => {
    cy.get('[data-test=collapse-all-btn]')
      .contains('Collapse All')
      .click();

    cy.get('#grid27')
      .find('.slick-group-toggle.expanded')
      .should('have.length', 0);

    cy.get('#grid27')
      .find('.slick-group-toggle.collapsed')
      .should(($rows) => expect($rows).to.have.length.greaterThan(0));
  });

  it('should expand all rows from "Expand All" button', () => {
    cy.get('[data-test=expand-all-btn]')
      .contains('Expand All')
      .click();

    cy.get('#grid27')
      .find('.slick-group-toggle.collapsed')
      .should('have.length', 0);

    cy.get('#grid27')
      .find('.slick-group-toggle.expanded')
      .should(($rows) => expect($rows).to.have.length.greaterThan(0));
  });

  it('should collapsed all rows from "Collapse All" context menu', () => {
    cy.get('#grid27')
      .contains('5 days');

    cy.get('#grid27')
      .find('.slick-row .slick-cell:nth(1)')
      .rightclick({ force: true });

    cy.get('.slick-context-menu.dropright .slick-context-menu-command-list')
      .find('.slick-context-menu-item')
      .find('.slick-context-menu-content')
      .contains('Collapse all Groups')
      .click();

    cy.get('#grid27')
      .find('.slick-group-toggle.expanded')
      .should('have.length', 0);

    cy.get('#grid27')
      .find('.slick-group-toggle.collapsed')
      .should(($rows) => expect($rows).to.have.length.greaterThan(0));
  });

  it('should collapsed all rows from "Expand All" context menu', () => {
    cy.get('#grid27')
      .contains('5 days');

    cy.get('#grid27')
      .find('.slick-row .slick-cell:nth(1)')
      .rightclick({ force: true });

    cy.get('.slick-context-menu.dropright .slick-context-menu-command-list')
      .find('.slick-context-menu-item')
      .find('.slick-context-menu-content')
      .contains('Expand all Groups')
      .click();

    cy.get('#grid27')
      .find('.slick-group-toggle.collapsed')
      .should('have.length', 0);

    cy.get('#grid27')
      .find('.slick-group-toggle.expanded')
      .should(($rows) => expect($rows).to.have.length.greaterThan(0));
  });

  it('should have data filtered, with "% Complete" >=25, and not show the full item count in the footer', () => {
    cy.get('.search-filter.filter-percentComplete .operator .form-control')
      .should('have.value', '>=');

    cy.get('.rangeInput_percentComplete')
      .invoke('val')
      .then(text => expect(text).to.eq('25'));

    cy.get('.search-filter .input-group-text')
      .should($span => expect($span.text()).to.eq('25'));

    cy.get('.right-footer')
      .should($span => {
        const text = removeExtraSpaces($span.text()); // remove all white spaces
        expect(text).not.to.eq('500 of 500 items');
      });
  });

  it('should open the Grid Menu "Clear all Filters" command', () => {
    cy.get('#grid27')
      .find('button.slick-gridmenu-button')
      .trigger('click')
      .click();

    cy.get(`.slick-gridmenu:visible`)
      .find('.slick-gridmenu-item')
      .first()
      .find('span')
      .contains('Clear all Filters')
      .click();
  });

  it('should no longer have filters and it should show the full item count in the footer', () => {
    cy.get('.search-filter.filter-percentComplete .operator .form-control')
      .should('have.value', '');

    cy.get('.rangeInput_percentComplete')
      .invoke('val')
      .then(text => expect(text).to.eq('0'));

    cy.get('.search-filter .input-group-text')
      .should($span => expect($span.text()).to.eq('0'));

    cy.get('.right-footer')
      .should($span => {
        const text = removeExtraSpaces($span.text()); // remove all white spaces
        expect(text).to.eq('500 of 500 items');
      });
  });

  it('should click on the "Dynamically Change Filter" button and expect all child items to have a "% Complete" lower than 40', () => {
    cy.get('[data-test="change-filter-dynamically"]').click();
    cy.get('[data-test=expand-all-btn]')
      .contains('Expand All')
      .click();

    const readLineCount = 10;
    for (let row = 0; row < readLineCount; row++) {
      cy.get(`[style="top:${GRID_ROW_HEIGHT * row}px"]`)
        .should($elm => {
          // only read the percent complete value if it's not a parent
          const $slickGroupToggleNotExpanded = $elm.children('.slick-cell:nth(0)').children('.slick-group-toggle:not(.expanded)');
          if ($slickGroupToggleNotExpanded.length > 1) {
            const percentComplete = $elm.children('.slick-cell:nth(2)').first('div.percent-complete-bar-with-text').text();
            expect(+percentComplete).to.be.lt(40)
          }
        });
    }
  });

  it('should open the Grid Menu "Clear all Filters" command', () => {
    cy.get('#grid27')
      .find('button.slick-gridmenu-button')
      .trigger('click')
      .click();

    cy.get(`.slick-gridmenu:visible`)
      .find('.slick-gridmenu-item')
      .first()
      .find('span')
      .contains('Clear all Filters')
      .click();
  });

  it('should add an item (Task 500) in the first parent it finds and so we should expect it to be inserted at tree level 1', () => {
    cy.get('[data-test=add-item-btn]')
      .contains('Add New Item')
      .click();

    cy.get('.slick-tree-title[level=1]')
      .get('.slick-cell')
      .contains('Task 500');
  });

  it('should collapse the Tree and not expect to see the newly inserted item (Task 500) because all child will be collapsed', () => {
    cy.get('[data-test=collapse-all-btn]')
      .contains('Collapse All')
      .click();

    cy.get('.slick-tree-title[level=1]')
      .should('have.length', 0);

    cy.get('.slick-tree-title')
      .get('.slick-cell')
      .contains(/^((?!Task 500).)*$/);
  });
});
<commit_msg>test: fix Cypress failing test<commit_after>/// <reference types="cypress" />

function removeExtraSpaces(text) {
  return `${text}`.replace(/\s+/g, ' ').trim();
}

describe('Example 27 - Tree Data (from a Hierarchical Dataset)', { retries: 1 }, () => {
  const GRID_ROW_HEIGHT = 45;
  const titles = ['Title', 'Duration', '% Complete', 'Start', 'Finish', 'Effort Driven'];

  it('should display Example title', () => {
    cy.visit(`${Cypress.config('baseExampleUrl')}/example27`);
    cy.get('h2').should('contain', 'Example 27: Tree Data (from a flat dataset with parentId references)');
  });

  it('should have exact column titles in grid', () => {
    cy.get('#grid27')
      .find('.slick-header-columns')
      .children()
      .each(($child, index) => expect($child.text()).to.eq(titles[index]));
  });

  it('should have a Grid Preset Filter on 3rd column "% Complete" and expect all rows to be filtered as well', () => {
    cy.get('.input-group-text.rangeOutput_percentComplete')
      .contains('25');

    cy.get('.search-filter.filter-percentComplete')
      .find('.input-group-addon.operator select')
      .contains('>=');
  });

  it('should collapsed all rows from "Collapse All" button', () => {
    cy.get('[data-test=collapse-all-btn]')
      .contains('Collapse All')
      .click();

    cy.get('#grid27')
      .find('.slick-group-toggle.expanded')
      .should('have.length', 0);

    cy.get('#grid27')
      .find('.slick-group-toggle.collapsed')
      .should(($rows) => expect($rows).to.have.length.greaterThan(0));
  });

  it('should expand all rows from "Expand All" button', () => {
    cy.get('[data-test=expand-all-btn]')
      .contains('Expand All')
      .click();

    cy.get('#grid27')
      .find('.slick-group-toggle.collapsed')
      .should('have.length', 0);

    cy.get('#grid27')
      .find('.slick-group-toggle.expanded')
      .should(($rows) => expect($rows).to.have.length.greaterThan(0));
  });

  it('should collapsed all rows from "Collapse All" context menu', () => {
    cy.get('#grid27')
      .contains('5 days');

    cy.get('#grid27')
      .find('.slick-row .slick-cell:nth(1)')
      .rightclick({ force: true });

    cy.get('.slick-context-menu.dropright .slick-context-menu-command-list')
      .find('.slick-context-menu-item')
      .find('.slick-context-menu-content')
      .contains('Collapse all Groups')
      .click();

    cy.get('#grid27')
      .find('.slick-group-toggle.expanded')
      .should('have.length', 0);

    cy.get('#grid27')
      .find('.slick-group-toggle.collapsed')
      .should(($rows) => expect($rows).to.have.length.greaterThan(0));
  });

  it('should collapsed all rows from "Expand All" context menu', () => {
    cy.get('#grid27')
      .contains('5 days');

    cy.get('#grid27')
      .find('.slick-row .slick-cell:nth(1)')
      .rightclick({ force: true });

    cy.get('.slick-context-menu.dropright .slick-context-menu-command-list')
      .find('.slick-context-menu-item')
      .find('.slick-context-menu-content')
      .contains('Expand all Groups')
      .click();

    cy.get('#grid27')
      .find('.slick-group-toggle.collapsed')
      .should('have.length', 0);

    cy.get('#grid27')
      .find('.slick-group-toggle.expanded')
      .should(($rows) => expect($rows).to.have.length.greaterThan(0));
  });

  it('should have data filtered, with "% Complete" >=25, and not show the full item count in the footer', () => {
    cy.get('.search-filter.filter-percentComplete .operator .form-control')
      .should('have.value', '>=');

    cy.get('.rangeInput_percentComplete')
      .invoke('val')
      .then(text => expect(text).to.eq('25'));

    cy.get('.search-filter .input-group-text')
      .should($span => expect($span.text()).to.eq('25'));

    cy.get('.right-footer')
      .should($span => {
        const text = removeExtraSpaces($span.text()); // remove all white spaces
        expect(text).not.to.eq('500 of 500 items');
      });
  });

  it('should open the Grid Menu "Clear all Filters" command', () => {
    cy.get('#grid27')
      .find('button.slick-gridmenu-button')
      .trigger('click')
      .click();

    cy.get(`.slick-gridmenu:visible`)
      .find('.slick-gridmenu-item')
      .first()
      .find('span')
      .contains('Clear all Filters')
      .click();
  });

  it('should no longer have filters and it should show the full item count in the footer', () => {
    cy.get('.search-filter.filter-percentComplete .operator .form-control')
      .should('have.value', null);

    cy.get('.rangeInput_percentComplete')
      .invoke('val')
      .then(text => expect(text).to.eq('0'));

    cy.get('.search-filter .input-group-text')
      .should($span => expect($span.text()).to.eq('0'));

    cy.get('.right-footer')
      .should($span => {
        const text = removeExtraSpaces($span.text()); // remove all white spaces
        expect(text).to.eq('500 of 500 items');
      });
  });

  it('should click on the "Dynamically Change Filter" button and expect all child items to have a "% Complete" lower than 40', () => {
    cy.get('[data-test="change-filter-dynamically"]').click();
    cy.get('[data-test=expand-all-btn]')
      .contains('Expand All')
      .click();

    const readLineCount = 10;
    for (let row = 0; row < readLineCount; row++) {
      cy.get(`[style="top:${GRID_ROW_HEIGHT * row}px"]`)
        .should($elm => {
          // only read the percent complete value if it's not a parent
          const $slickGroupToggleNotExpanded = $elm.children('.slick-cell:nth(0)').children('.slick-group-toggle:not(.expanded)');
          if ($slickGroupToggleNotExpanded.length > 1) {
            const percentComplete = $elm.children('.slick-cell:nth(2)').first('div.percent-complete-bar-with-text').text();
            expect(+percentComplete).to.be.lt(40)
          }
        });
    }
  });

  it('should open the Grid Menu "Clear all Filters" command', () => {
    cy.get('#grid27')
      .find('button.slick-gridmenu-button')
      .trigger('click')
      .click();

    cy.get(`.slick-gridmenu:visible`)
      .find('.slick-gridmenu-item')
      .first()
      .find('span')
      .contains('Clear all Filters')
      .click();
  });

  it('should add an item (Task 500) in the first parent it finds and so we should expect it to be inserted at tree level 1', () => {
    cy.get('[data-test=add-item-btn]')
      .contains('Add New Item')
      .click();

    cy.get('.slick-tree-title[level=1]')
      .get('.slick-cell')
      .contains('Task 500');
  });

  it('should collapse the Tree and not expect to see the newly inserted item (Task 500) because all child will be collapsed', () => {
    cy.get('[data-test=collapse-all-btn]')
      .contains('Collapse All')
      .click();

    cy.get('.slick-tree-title[level=1]')
      .should('have.length', 0);

    cy.get('.slick-tree-title')
      .get('.slick-cell')
      .contains(/^((?!Task 500).)*$/);
  });
});
<|endoftext|>


<commit_before>// @flow
import * as Constants from '../constants/chat'
import HiddenString from '../util/hidden-string'
import engine from '../engine'
import {CommonMessageType, CommonTLFVisibility, LocalMessageUnboxedState, NotifyChatChatActivityType, localGetInboxAndUnboxLocalRpcPromise, localGetThreadLocalRpcPromise, localPostLocalRpcPromise} from '../constants/types/flow-types-chat'
import {List, Map} from 'immutable'
import {call, put, select} from 'redux-saga/effects'
import {safeTakeEvery, safeTakeLatest} from '../util/saga'
import {usernameSelector} from '../constants/selectors'

import type {ConversationIDKey, InboxState, IncomingMessage, LoadInbox, LoadMoreMessages, LoadedInbox, Message, PostMessage, SelectConversation, SetupNewChatHandler} from '../constants/chat'
import type {GetInboxAndUnboxLocalRes, IncomingMessage as IncomingMessageRPCType, MessageUnboxed} from '../constants/types/flow-types-chat'
import type {SagaGenerator} from '../constants/types/saga'
import type {TypedState} from '../constants/reducer'

const {conversationIDToKey, keyToConversationID, InboxStateRecord} = Constants

function postMessage (conversationIDKey: ConversationIDKey, text: HiddenString): PostMessage {
  return {type: Constants.postMessage, payload: {conversationIDKey, text}}
}

function setupNewChatHandler (): SetupNewChatHandler {
  return {type: Constants.setupNewChatHandler, payload: undefined}
}

function loadInbox (): LoadInbox {
  return {type: Constants.loadInbox, payload: undefined}
}

function loadMoreMessages (): LoadMoreMessages {
  return {type: Constants.loadMoreMessages, payload: undefined}
}

function selectConversation (conversationIDKey: ConversationIDKey): SelectConversation {
  return {type: Constants.selectConversation, payload: {conversationIDKey}}
}

// This is emoji aware hence all the weird ... stuff. See https://mathiasbynens.be/notes/javascript-unicode#iterating-over-symbols
function _makeSnippet (message, max) {
  // $FlowIssue flow doesn't understand spread + strings
  return [...(message.substring(0, max * 4).replace(/\s+/g, ' '))].slice(0, max).join('')
}

function _inboxToConversations (inbox: GetInboxAndUnboxLocalRes): List<InboxState> {
  return List((inbox.conversations || []).map(convo => {
    if (!convo.info.id) {
      return null
    }

    const recentMessage: ?MessageUnboxed = (convo.maxMessages || []).find(message => (
      message.state === LocalMessageUnboxedState.valid &&
      message.valid &&
      message.valid.messageBody.messageType === CommonMessageType.text
    ))

    let snippet
    try {
      // $FlowIssue doens't understand try
      snippet = _makeSnippet(recentMessage.valid.messageBody.text.body, 100)
    } catch (_) { }

    return new InboxStateRecord({
      info: convo.info,
      conversationIDKey: conversationIDToKey(convo.info.id),
      participants: List(convo.info.writerNames || []), // TODO in recent order... somehow
      muted: false, // TODO
      time: 'Time', // TODO
      snippet,
    })
  }).filter(Boolean))
}

function * _postMessage (action: PostMessage): SagaGenerator<any, any> {
  const infoSelector = (state: TypedState) => {
    const convo = state.chat.get('inbox').find(convo => convo.get('conversationIDKey') === action.payload.conversationIDKey)
    if (convo) {
      return convo.get('info')
    }
    return null
  }

  const info = yield select(infoSelector)

  if (!info) {
    console.warn('No info to postmessage!')
    return
  }

  const configSelector = (state: TypedState) => {
    try {
      return {
        // $FlowIssue doesn't understand try
        deviceID: state.config.extendedConfig.device.deviceID,
        uid: state.config.uid,
      }
    } catch (_) {
      return {
        deviceID: '',
        uid: '',
      }
    }
  }

  const {deviceID, uid}: {deviceID: string, uid: string} = ((yield select(configSelector)): any)

  if (!deviceID || !uid) {
    console.warn('No deviceid/uid to postmessage!')
    return
  }

  const clientHeader = {
    conv: info.triple,
    tlfName: info.tlfName,
    tlfPublic: info.visibility === CommonTLFVisibility.public,
    messageType: CommonMessageType.text,
    supersedes: 0,
    sender: Buffer.from(uid, 'hex'),
    senderDevice: Buffer.from(deviceID, 'hex'),
  }

  yield call(localPostLocalRpcPromise, {
    param: {
      conversationID: keyToConversationID(action.payload.conversationIDKey),
      msg: {
        clientHeader,
        messageBody: {
          messageType: CommonMessageType.text,
          text: {
            body: action.payload.text.stringValue(),
          },
        },
      },
    },
  })
}

function * _incomingMessage (action: IncomingMessage): SagaGenerator<any, any> {
  if (action.payload.activity.activityType === NotifyChatChatActivityType.incomingMessage) {
    const incomingMessage: ?IncomingMessageRPCType = action.payload.activity.incomingMessage
    if (incomingMessage) {
      const messageUnboxed: MessageUnboxed = incomingMessage.message
      const yourName = yield select(usernameSelector)
      const message = _unboxedToMessage(messageUnboxed, 0, yourName)

      yield put({
        type: Constants.appendMessages,
        payload: {
          conversationIDKey: conversationIDToKey(incomingMessage.convID),
          messages: [message],
        },
      })
    }
  }
}

function * _setupNewChatHandler (): SagaGenerator<any, any> {
  yield put((dispatch: Dispatch) => {
    engine().setIncomingHandler('chat.1.NotifyChat.NewChatActivity', ({uid, activity}) => {
      dispatch({type: Constants.incomingMessage, payload: {activity}})
    })
  })
}

function * _loadInbox (): SagaGenerator<any, any> {
  // $ForceType
  const inbox: GetInboxAndUnboxLocalRes = yield call(localGetInboxAndUnboxLocalRpcPromise, {params: {}})
  const conversations: List<InboxState> = _inboxToConversations(inbox)
  yield put({type: Constants.loadedInbox, payload: {inbox: conversations}})
}

function * _loadedInbox (action: LoadedInbox): SagaGenerator<any, any> {
  const selector = (state: TypedState) => state.chat.get('selectedConversation')
  const selectedConversation = yield select(selector)

  if (!selectedConversation) {
    if (action.payload.inbox.count()) {
      const mostRecentConversation = action.payload.inbox.get(0)
      yield put(selectConversation(mostRecentConversation.get('conversationIDKey')))
    }
  }
}

function * _loadMoreMessages (): SagaGenerator<any, any> {
  const selectedSelector = (state: TypedState) => state.chat.get('selectedConversation')
  const conversationIDKey = yield select(selectedSelector)

  if (!conversationIDKey) {
    return
  }

  const conversationID = keyToConversationID(conversationIDKey)
  const conversationStateSelector = (state: TypedState) => state.chat.get('conversationStates', Map()).get(conversationIDKey)
  const oldConversationState = yield select(conversationStateSelector)

  let next
  if (oldConversationState) {
    if (oldConversationState.get('isLoading')) {
      __DEV__ && console.log('Bailing on chat load more due to isloading already')
      return
    }

    if (!oldConversationState.get('moreToLoad')) {
      __DEV__ && console.log('Bailing on chat load more due to no more to load')
      return
    }

    next = oldConversationState.get('paginationNext', undefined)
  }

  yield put({type: Constants.loadingMessages, payload: {conversationIDKey}})

  const thread = yield call(localGetThreadLocalRpcPromise, {param: {
    conversationID,
    query: {markAsRead: true},
    pagination: {
      next,
      num: Constants.maxMessagesToLoadAtATime,
    },
  }})

  const yourName = yield select(usernameSelector)
  const messages = (thread && thread.thread && thread.thread.messages || []).map((message, idx) => _unboxedToMessage(message, idx, yourName)).reverse()
  const pagination = _threadToPagination(thread)

  yield put({
    type: Constants.prependMessages,
    payload: {
      conversationIDKey,
      messages,
      moreToLoad: !pagination.last,
      paginationNext: pagination.next,
    },
  })
}

function _threadToPagination (thread) {
  if (thread && thread.thread && thread.thread.pagination) {
    return thread.thread.pagination
  }
  return {
    last: undefined,
    next: undefined,
  }
}

function _unboxedToMessage (message: MessageUnboxed, idx: number, yourName): Message {
  if (message.state === LocalMessageUnboxedState.valid) {
    const payload = message.valid
    if (payload) {
      const common = {
        author: payload.senderUsername,
        timestamp: payload.serverHeader.ctime,
        messageID: payload.serverHeader.messageID,
      }

      const isYou = common.author === yourName

      switch (payload.messageBody.messageType) {
        case CommonMessageType.text:
          return {
            type: 'Text',
            ...common,
            message: new HiddenString(payload.messageBody && payload.messageBody.text && payload.messageBody.text.body || ''),
            followState: isYou ? 'You' : 'Following', // TODO get this
            messageState: 'sent',
          }
        default:
          return {
            ...common,
            type: 'Unhandled',
          }
      }
    }
  }
  return {
    type: 'Error', // TODO
    messageID: idx,
    reason: 'temp',
  }
}

function * _selectConversation (action: SelectConversation): SagaGenerator<any, any> {
  yield put(loadMoreMessages())
}

function * chatSaga (): SagaGenerator<any, any> {
  yield [
    safeTakeLatest(Constants.loadInbox, _loadInbox),
    safeTakeLatest(Constants.loadedInbox, _loadedInbox),
    safeTakeEvery(Constants.loadMoreMessages, _loadMoreMessages),
    safeTakeLatest(Constants.selectConversation, _selectConversation),
    safeTakeEvery(Constants.setupNewChatHandler, _setupNewChatHandler),
    safeTakeEvery(Constants.incomingMessage, _incomingMessage),
    safeTakeEvery(Constants.postMessage, _postMessage),
  ]
}

export default chatSaga

export {
  loadInbox,
  loadMoreMessages,
  selectConversation,
  setupNewChatHandler,
  postMessage,
}
<commit_msg>send nonblocking<commit_after>// @flow
import * as Constants from '../constants/chat'
import HiddenString from '../util/hidden-string'
import engine from '../engine'
import {CommonMessageType, CommonTLFVisibility, LocalMessageUnboxedState, NotifyChatChatActivityType, localGetInboxAndUnboxLocalRpcPromise, localGetThreadLocalRpcPromise, localPostLocalNonblockRpcPromise} from '../constants/types/flow-types-chat'
import {List, Map} from 'immutable'
import {call, put, select} from 'redux-saga/effects'
import {safeTakeEvery, safeTakeLatest} from '../util/saga'
import {usernameSelector} from '../constants/selectors'

import type {ConversationIDKey, InboxState, IncomingMessage, LoadInbox, LoadMoreMessages, LoadedInbox, Message, PostMessage, SelectConversation, SetupNewChatHandler} from '../constants/chat'
import type {GetInboxAndUnboxLocalRes, IncomingMessage as IncomingMessageRPCType, MessageUnboxed} from '../constants/types/flow-types-chat'
import type {SagaGenerator} from '../constants/types/saga'
import type {TypedState} from '../constants/reducer'

const {conversationIDToKey, keyToConversationID, InboxStateRecord} = Constants

function postMessage (conversationIDKey: ConversationIDKey, text: HiddenString): PostMessage {
  return {type: Constants.postMessage, payload: {conversationIDKey, text}}
}

function setupNewChatHandler (): SetupNewChatHandler {
  return {type: Constants.setupNewChatHandler, payload: undefined}
}

function loadInbox (): LoadInbox {
  return {type: Constants.loadInbox, payload: undefined}
}

function loadMoreMessages (): LoadMoreMessages {
  return {type: Constants.loadMoreMessages, payload: undefined}
}

function selectConversation (conversationIDKey: ConversationIDKey): SelectConversation {
  return {type: Constants.selectConversation, payload: {conversationIDKey}}
}

// This is emoji aware hence all the weird ... stuff. See https://mathiasbynens.be/notes/javascript-unicode#iterating-over-symbols
function _makeSnippet (message, max) {
  // $FlowIssue flow doesn't understand spread + strings
  return [...(message.substring(0, max * 4).replace(/\s+/g, ' '))].slice(0, max).join('')
}

function _inboxToConversations (inbox: GetInboxAndUnboxLocalRes): List<InboxState> {
  return List((inbox.conversations || []).map(convo => {
    if (!convo.info.id) {
      return null
    }

    const recentMessage: ?MessageUnboxed = (convo.maxMessages || []).find(message => (
      message.state === LocalMessageUnboxedState.valid &&
      message.valid &&
      message.valid.messageBody.messageType === CommonMessageType.text
    ))

    let snippet
    try {
      // $FlowIssue doens't understand try
      snippet = _makeSnippet(recentMessage.valid.messageBody.text.body, 100)
    } catch (_) { }

    return new InboxStateRecord({
      info: convo.info,
      conversationIDKey: conversationIDToKey(convo.info.id),
      participants: List(convo.info.writerNames || []), // TODO in recent order... somehow
      muted: false, // TODO
      time: 'Time', // TODO
      snippet,
    })
  }).filter(Boolean))
}

function * _postMessage (action: PostMessage): SagaGenerator<any, any> {
  const infoSelector = (state: TypedState) => {
    const convo = state.chat.get('inbox').find(convo => convo.get('conversationIDKey') === action.payload.conversationIDKey)
    if (convo) {
      return convo.get('info')
    }
    return null
  }

  const info = yield select(infoSelector)

  if (!info) {
    console.warn('No info to postmessage!')
    return
  }

  const configSelector = (state: TypedState) => {
    try {
      return {
        // $FlowIssue doesn't understand try
        deviceID: state.config.extendedConfig.device.deviceID,
        uid: state.config.uid,
      }
    } catch (_) {
      return {
        deviceID: '',
        uid: '',
      }
    }
  }

  const {deviceID, uid}: {deviceID: string, uid: string} = ((yield select(configSelector)): any)

  if (!deviceID || !uid) {
    console.warn('No deviceid/uid to postmessage!')
    return
  }

  const clientHeader = {
    conv: info.triple,
    tlfName: info.tlfName,
    tlfPublic: info.visibility === CommonTLFVisibility.public,
    messageType: CommonMessageType.text,
    supersedes: 0,
    sender: Buffer.from(uid, 'hex'),
    senderDevice: Buffer.from(deviceID, 'hex'),
  }

  const sent = yield call(localPostLocalNonblockRpcPromise, {
    param: {
      conversationID: keyToConversationID(action.payload.conversationIDKey),
      msg: {
        clientHeader,
        messageBody: {
          messageType: CommonMessageType.text,
          text: {
            body: action.payload.text.stringValue(),
          },
        },
      },
    },
  })
  if (sent) {
    console.warn(sent.outboxID)
    console.warn(sent.outboxID.toString('hex'))
  }
}

function * _incomingMessage (action: IncomingMessage): SagaGenerator<any, any> {
  if (action.payload.activity.activityType === NotifyChatChatActivityType.incomingMessage) {
    const incomingMessage: ?IncomingMessageRPCType = action.payload.activity.incomingMessage
    if (incomingMessage) {
      const messageUnboxed: MessageUnboxed = incomingMessage.message
      const yourName = yield select(usernameSelector)
      const message = _unboxedToMessage(messageUnboxed, 0, yourName)

      yield put({
        type: Constants.appendMessages,
        payload: {
          conversationIDKey: conversationIDToKey(incomingMessage.convID),
          messages: [message],
        },
      })
    }
  }
}

function * _setupNewChatHandler (): SagaGenerator<any, any> {
  yield put((dispatch: Dispatch) => {
    engine().setIncomingHandler('chat.1.NotifyChat.NewChatActivity', ({uid, activity}) => {
      dispatch({type: Constants.incomingMessage, payload: {activity}})
    })
  })
}

function * _loadInbox (): SagaGenerator<any, any> {
  // $ForceType
  const inbox: GetInboxAndUnboxLocalRes = yield call(localGetInboxAndUnboxLocalRpcPromise, {params: {}})
  const conversations: List<InboxState> = _inboxToConversations(inbox)
  yield put({type: Constants.loadedInbox, payload: {inbox: conversations}})
}

function * _loadedInbox (action: LoadedInbox): SagaGenerator<any, any> {
  const selector = (state: TypedState) => state.chat.get('selectedConversation')
  const selectedConversation = yield select(selector)

  if (!selectedConversation) {
    if (action.payload.inbox.count()) {
      const mostRecentConversation = action.payload.inbox.get(0)
      yield put(selectConversation(mostRecentConversation.get('conversationIDKey')))
    }
  }
}

function * _loadMoreMessages (): SagaGenerator<any, any> {
  const selectedSelector = (state: TypedState) => state.chat.get('selectedConversation')
  const conversationIDKey = yield select(selectedSelector)

  if (!conversationIDKey) {
    return
  }

  const conversationID = keyToConversationID(conversationIDKey)
  const conversationStateSelector = (state: TypedState) => state.chat.get('conversationStates', Map()).get(conversationIDKey)
  const oldConversationState = yield select(conversationStateSelector)

  let next
  if (oldConversationState) {
    if (oldConversationState.get('isLoading')) {
      __DEV__ && console.log('Bailing on chat load more due to isloading already')
      return
    }

    if (!oldConversationState.get('moreToLoad')) {
      __DEV__ && console.log('Bailing on chat load more due to no more to load')
      return
    }

    next = oldConversationState.get('paginationNext', undefined)
  }

  yield put({type: Constants.loadingMessages, payload: {conversationIDKey}})

  const thread = yield call(localGetThreadLocalRpcPromise, {param: {
    conversationID,
    query: {markAsRead: true},
    pagination: {
      next,
      num: Constants.maxMessagesToLoadAtATime,
    },
  }})

  const yourName = yield select(usernameSelector)
  const messages = (thread && thread.thread && thread.thread.messages || []).map((message, idx) => _unboxedToMessage(message, idx, yourName)).reverse()
  const pagination = _threadToPagination(thread)

  yield put({
    type: Constants.prependMessages,
    payload: {
      conversationIDKey,
      messages,
      moreToLoad: !pagination.last,
      paginationNext: pagination.next,
    },
  })
}

function _threadToPagination (thread) {
  if (thread && thread.thread && thread.thread.pagination) {
    return thread.thread.pagination
  }
  return {
    last: undefined,
    next: undefined,
  }
}

function _unboxedToMessage (message: MessageUnboxed, idx: number, yourName): Message {
  if (message.state === LocalMessageUnboxedState.valid) {
    const payload = message.valid
    if (payload) {
      const common = {
        author: payload.senderUsername,
        timestamp: payload.serverHeader.ctime,
        messageID: payload.serverHeader.messageID,
      }

      const isYou = common.author === yourName

      switch (payload.messageBody.messageType) {
        case CommonMessageType.text:
          return {
            type: 'Text',
            ...common,
            message: new HiddenString(payload.messageBody && payload.messageBody.text && payload.messageBody.text.body || ''),
            followState: isYou ? 'You' : 'Following', // TODO get this
            messageState: 'sent',
          }
        default:
          return {
            ...common,
            type: 'Unhandled',
          }
      }
    }
  }
  return {
    type: 'Error', // TODO
    messageID: idx,
    reason: 'temp',
  }
}

function * _selectConversation (action: SelectConversation): SagaGenerator<any, any> {
  yield put(loadMoreMessages())
}

function * chatSaga (): SagaGenerator<any, any> {
  yield [
    safeTakeLatest(Constants.loadInbox, _loadInbox),
    safeTakeLatest(Constants.loadedInbox, _loadedInbox),
    safeTakeEvery(Constants.loadMoreMessages, _loadMoreMessages),
    safeTakeLatest(Constants.selectConversation, _selectConversation),
    safeTakeEvery(Constants.setupNewChatHandler, _setupNewChatHandler),
    safeTakeEvery(Constants.incomingMessage, _incomingMessage),
    safeTakeEvery(Constants.postMessage, _postMessage),
  ]
}

export default chatSaga

export {
  loadInbox,
  loadMoreMessages,
  selectConversation,
  setupNewChatHandler,
  postMessage,
}
<|endoftext|>


<commit_before>/**
 * Get the version of the extension.
 * @return {Object} Object with major, minor, and patch fields
 */
function getExtensionVersion() {
	var details = chrome.app.getDetails();
	var versionString = details.version;

	var array = versionString.split('.');

	var version = {
		major: array[0],
		minor: array[1],
		patch: array[2]
	}

	return version;
}

function domainRegex(url) {
	var domainRegex = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/igm;
	var result = domainRegex.exec(url);

	return result;
}

function getDomain(url) {
	return /^((([^:\/?#]+):)?(\/\/([^\/?#]*))?)/igm.exec(url)[0];
}

function getHostname(url) {
	var hostname = domainRegex(url)[4];

	if (hostname.substring(0, 4) == "www.") {
		hostname = hostname.substring(4);
	}

	return hostname;
}

function loop(iteration, end, operation, finishCallback) {
	if (iteration < end) {
		operation(iteration, function() {
			loop(iteration + 1, end, operation, finishCallback);
		});
	} else {
		finishCallback();
	}
}

function async_loop(start, end, operation, finishCallback) {
	var operationsToComplete = (end - start);
	for (var i = start; i < end; i++) {
		operation(i, function() {
			operationsToComplete--;
			if (operationsToComplete == 0) {
				finishCallback();
			}
		});
	}
}

function makeHTTPRequest(url, successCallback, errorCallback) {
	var http = new XMLHttpRequest();

	http.ontimeout = function() {
		return errorCallback(null);
	}

	http.onreadystatechange = function() {
		if (http.readyState == 4) {
			if (http.status == 200) {
				return successCallback(http.responseText, http.getResponseHeader('content-type'));
			} else {
				return errorCallback(http.status);
			}
		}
	}

	http.open('GET', url, true);
	http.timeout = 500;
	http.send(null);
}

/**
 * Returns {true} if the URL is a Chrome URL.
 * @param  {[type]}  url The URL to check it it's a Chrome URL.
 * @return {Boolean} Returns {true} if the URL is a Chrome URL.
 */
function isChromeURL(url) {
	return url.substring(0, 6) == 'chrome';
}

/**
 * Returns {true} if the URL is a Chrome extension URL.
 * @param  {String}  url The URL to check if it's a Chrome Extension
 *     URL.
 * @return {Boolean} Returns {true} if the URL is a Chrome Extension
 *     URL.
 */
function isExtensionURL(url) {
	var baseURL = chrome.extension.getURL("/");
	var newTabURL = "chrome://newtab";

	if (url.substring(0, newTabURL.length) == newTabURL) {
		return true;
	}

	return url.substring(0, baseURL.length) == baseURL;
}

Array.prototype.firstIndexOfElementEqualTo = function(element) {
	for (var i = 0; i < this.length; i++) {
		if (this[i] == element) {
			return i;
		}
	}

	return -1;
}

Array.prototype.removeElementEqualTo = function(element) {
	for (var i = 0; i < this.length; i++) {
		if (this[i] == element) {
			return this.removeAtIndex(i);
		}
	}
}

Array.prototype.removeAtIndex = function(index) {
	return this.splice(index, 1)[0];
};

Array.prototype.insertAtIndex = function(element, index) {
	this.splice(index, 0, element);
}<commit_msg>Always return finihCallback().<commit_after>/**
 * Get the version of the extension.
 * @return {Object} Object with major, minor, and patch fields
 */
function getExtensionVersion() {
	var details = chrome.app.getDetails();
	var versionString = details.version;

	var array = versionString.split('.');

	var version = {
		major: array[0],
		minor: array[1],
		patch: array[2]
	}

	return version;
}

function domainRegex(url) {
	var domainRegex = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/igm;
	var result = domainRegex.exec(url);

	return result;
}

function getDomain(url) {
	return /^((([^:\/?#]+):)?(\/\/([^\/?#]*))?)/igm.exec(url)[0];
}

function getHostname(url) {
	var hostname = domainRegex(url)[4];

	if (hostname.substring(0, 4) == "www.") {
		hostname = hostname.substring(4);
	}

	return hostname;
}

function loop(iteration, end, operation, finishCallback) {
	if (iteration < end) {
		operation(iteration, function() {
			loop(iteration + 1, end, operation, finishCallback);
		});
	} else {
		return finishCallback();
	}
}

function async_loop(start, end, operation, finishCallback) {
	var operationsToComplete = (end - start);
	for (var i = start; i < end; i++) {
		operation(i, function() {
			operationsToComplete--;
			if (operationsToComplete == 0) {
				return finishCallback();
			}
		});
	}
}

function makeHTTPRequest(url, successCallback, errorCallback) {
	var http = new XMLHttpRequest();

	http.ontimeout = function() {
		return errorCallback(null);
	}

	http.onreadystatechange = function() {
		if (http.readyState == 4) {
			if (http.status == 200) {
				return successCallback(http.responseText, http.getResponseHeader('content-type'));
			} else {
				return errorCallback(http.status);
			}
		}
	}

	http.open('GET', url, true);
	http.timeout = 500;
	http.send(null);
}

/**
 * Returns {true} if the URL is a Chrome URL.
 * @param  {[type]}  url The URL to check it it's a Chrome URL.
 * @return {Boolean} Returns {true} if the URL is a Chrome URL.
 */
function isChromeURL(url) {
	return url.substring(0, 6) == 'chrome';
}

/**
 * Returns {true} if the URL is a Chrome extension URL.
 * @param  {String}  url The URL to check if it's a Chrome Extension
 *     URL.
 * @return {Boolean} Returns {true} if the URL is a Chrome Extension
 *     URL.
 */
function isExtensionURL(url) {
	var baseURL = chrome.extension.getURL("/");
	var newTabURL = "chrome://newtab";

	if (url.substring(0, newTabURL.length) == newTabURL) {
		return true;
	}

	return url.substring(0, baseURL.length) == baseURL;
}

Array.prototype.firstIndexOfElementEqualTo = function(element) {
	for (var i = 0; i < this.length; i++) {
		if (this[i] == element) {
			return i;
		}
	}

	return -1;
}

Array.prototype.removeElementEqualTo = function(element) {
	for (var i = 0; i < this.length; i++) {
		if (this[i] == element) {
			return this.removeAtIndex(i);
		}
	}
}

Array.prototype.removeAtIndex = function(index) {
	return this.splice(index, 1)[0];
};

Array.prototype.insertAtIndex = function(element, index) {
	this.splice(index, 0, element);
}<|endoftext|>


<commit_before>import { EventEmitter } from 'events';

const ANSWER_THRESHOLD = 10;

/**
 * Base class of all classes
 */
export default class Base extends EventEmitter {
  constructor(method) {
    super();

    this.method = method;
    this.properties = {};
  }

  /**
   * Sends the message, you should only use this method yourself if
   * you are extending this class. Normally you should call bot.send(message)
   *
   * Events: message:sent => Emitted after sending the message to API, gets the
   * 												  API's response
   *
   *			message:answer => Emitted when your message gets an answer from
   *				                 the contact (reply in case of groups)
   *				                 gets the Update object containing message
   *
   * @param  {object} bot
   * @param {boolean} expectAnswer whether a sent message expects an answer from a contact(s)
   * @return {Promise} returns a promise, resolved with message:answer
   */
  send(bot, expectAnswer = false) {
    if (this._keyboard) {
      const replyMarkup = JSON.stringify(this._keyboard.getProperties());
      this.properties.reply_markup = replyMarkup;
    }

    return new Promise((resolve, reject) => {
      this._apiSend(bot)
        .then(response => {
          this.emit('message:sent', response);
          return response.result.message_id;
        })
        .then(messageId => {
          if (!expectAnswer) {
            // no need to add more event callbacks for the messages that don't need expect an answer
            return resolve();
          }

          const chat = this.properties.chat_id;
          let answers = 0;

          bot.on('update', function listener(result) {
            answers += result.length;

            const update = result.find(({ message }) => {
              // if in a group, there will be a reply to this message
              if (chat < 0) {
                return message.chat.id === chat
                  && message.reply_to_message
                  && message.reply_to_message.message_id === messageId;
              }

              return message && message.chat.id === chat;
            });

            if (update) {
              resolve(update.message);
              this.emit('message:answer', update.message);
              bot.removeListener('update', listener);
            }

            if (answers >= ANSWER_THRESHOLD) {
              bot.removeListener('update', listener);
            }
          });

          return void 0;
        })
        .catch(reject)
        .finally(() => {
          if (this._keyboard.one_time_keyboard) {
            this._keyboard.replyMarkup = '';
          }
        });
    });
  }

  _apiSend(bot) {
    return bot.api[this.method](this.properties);
  }

  /**
   * Returns properties of the object
   * @return {object} properties of object
   */
  getProperties() {
    return this.properties;
  }

  /**
   * Set properties of the object
   * @param {object} object properties to set
   * @param {boolean} extend A boolean indicating if the properties should be
   *                         extended by the object provided (Object.assign)
   *                         or properties should be replaced by the object
   *                         defaults to true
   * @return {object} returns the properties (same as getProperties)
   */
  setProperties(object, extend = true) {
    this.properties = extend ? Object.assign(this.properties, object)
                             : object;

    return this.getProperties();
  }
}
<commit_msg>fix: default  param value to true to avoid breaking people's existing code; small return value fix<commit_after>import { EventEmitter } from 'events';

const ANSWER_THRESHOLD = 10;

/**
 * Base class of all classes
 */
export default class Base extends EventEmitter {
  constructor(method) {
    super();

    this.method = method;
    this.properties = {};
  }

  /**
   * Sends the message, you should only use this method yourself if
   * you are extending this class. Normally you should call bot.send(message)
   *
   * Events: message:sent => Emitted after sending the message to API, gets the
   * 												  API's response
   *
   *			message:answer => Emitted when your message gets an answer from
   *				                 the contact (reply in case of groups)
   *				                 gets the Update object containing message
   *
   * @param  {object} bot
   * @param {boolean} expectAnswer whether a sent message expects an answer from a contact(s)
   * @return {Promise} returns a promise, resolved with message:answer
   */
  send(bot, expectAnswer = true) {
    if (this._keyboard) {
      const replyMarkup = JSON.stringify(this._keyboard.getProperties());
      this.properties.reply_markup = replyMarkup;
    }

    return new Promise((resolve, reject) => {
      this._apiSend(bot)
        .then(response => {
          this.emit('message:sent', response);
          return response.result.message_id;
        })
        .then(messageId => {
          if (!expectAnswer) {
            // no need to add more event callbacks for the messages that don't need expect an answer
            resolve();
            return;
          }

          const chat = this.properties.chat_id;
          let answers = 0;

          bot.on('update', function listener(result) {
            answers += result.length;

            const update = result.find(({ message }) => {
              // if in a group, there will be a reply to this message
              if (chat < 0) {
                return message.chat.id === chat
                  && message.reply_to_message
                  && message.reply_to_message.message_id === messageId;
              }

              return message && message.chat.id === chat;
            });

            if (update) {
              resolve(update.message);
              this.emit('message:answer', update.message);
              bot.removeListener('update', listener);
            }

            if (answers >= ANSWER_THRESHOLD) {
              bot.removeListener('update', listener);
            }
          });
        })
        .catch(reject)
        .finally(() => {
          if (this._keyboard.one_time_keyboard) {
            this._keyboard.replyMarkup = '';
          }
        });
    });
  }

  _apiSend(bot) {
    return bot.api[this.method](this.properties);
  }

  /**
   * Returns properties of the object
   * @return {object} properties of object
   */
  getProperties() {
    return this.properties;
  }

  /**
   * Set properties of the object
   * @param {object} object properties to set
   * @param {boolean} extend A boolean indicating if the properties should be
   *                         extended by the object provided (Object.assign)
   *                         or properties should be replaced by the object
   *                         defaults to true
   * @return {object} returns the properties (same as getProperties)
   */
  setProperties(object, extend = true) {
    this.properties = extend ? Object.assign(this.properties, object)
                             : object;

    return this.getProperties();
  }
}
<|endoftext|>


<commit_before>module.exports = new RegExp('^test-shell-.*/child.*');
<commit_msg>TBR: "javascript/release/core": Update for CL 4530<commit_after>module.exports = new RegExp('^test/child.*');
<|endoftext|>


<commit_before>const path = require('path');
const replaceLib = require('@ant-design/tools/lib/replaceLib');
const getWebpackConfig = require('@ant-design/tools/lib/getWebpackConfig');
const EsbuildPlugin = require('esbuild-webpack-plugin').default;
const { version } = require('../package.json');
const themeConfig = require('./themeConfig');

const { webpack } = getWebpackConfig;

const isDev = process.env.NODE_ENV === 'development';
const usePreact = process.env.REACT_ENV === 'preact';

function alertBabelConfig(rules) {
  rules.forEach(rule => {
    if (rule.loader && rule.loader === 'babel-loader') {
      if (rule.options.plugins.indexOf(replaceLib) === -1) {
        rule.options.plugins.push(replaceLib);
      }
      rule.options.plugins = rule.options.plugins.filter(
        plugin => !plugin.indexOf || plugin.indexOf('babel-plugin-add-module-exports') === -1,
      );
      // Add babel-plugin-add-react-displayname
      rule.options.plugins.push(require.resolve('babel-plugin-add-react-displayname'));
    } else if (rule.use) {
      alertBabelConfig(rule.use);
    }
  });
}

module.exports = {
  port: 8001,
  hash: true,
  source: {
    components: './components',
    docs: './docs',
    changelog: ['CHANGELOG.zh-CN.md', 'CHANGELOG.en-US.md'],
    'components/form/v3': ['components/form/v3.zh-CN.md', 'components/form/v3.en-US.md'],
    'docs/resources': ['./docs/resources.zh-CN.md', './docs/resources.en-US.md'],
  },
  theme: './site/theme',
  htmlTemplate: './site/theme/static/template.html',
  themeConfig,
  filePathMapper(filePath) {
    if (filePath === '/index.html') {
      return ['/index.html', '/index-cn.html'];
    }
    if (filePath.endsWith('/index.html')) {
      return [filePath, filePath.replace(/\/index\.html$/, '-cn/index.html')];
    }
    if (filePath !== '/404.html' && filePath !== '/index-cn.html') {
      return [filePath, filePath.replace(/\.html$/, '-cn.html')];
    }
    return filePath;
  },
  doraConfig: {
    verbose: true,
  },
  lessConfig: {
    javascriptEnabled: true,
  },
  webpackConfig(config) {
    config.resolve.alias = {
      'antd/lib': path.join(process.cwd(), 'components'),
      'antd/es': path.join(process.cwd(), 'components'),
      antd: path.join(process.cwd(), 'index'),
      site: path.join(process.cwd(), 'site'),
      'react-router': 'react-router/umd/ReactRouter',
      'react-intl': 'react-intl/dist',
    };

    config.externals = {
      'react-router-dom': 'ReactRouterDOM',
    };

    if (usePreact) {
      config.resolve.alias = {
        ...config.resolve.alias,
        react: 'preact-compat',
        'react-dom': 'preact-compat',
        'create-react-class': 'preact-compat/lib/create-react-class',
        'react-router': 'react-router',
      };
    }

    if (isDev) {
      config.devtool = 'source-map';

      // Resolve use react hook fail when yarn link or npm link
      // https://github.com/webpack/webpack/issues/8607#issuecomment-453068938
      config.resolve.alias = { ...config.resolve.alias, react: require.resolve('react') };
    } else if (process.env.ESBUILD) {
      // use esbuild
      config.optimization.minimizer = [new EsbuildPlugin()];
    }

    alertBabelConfig(config.module.rules);

    config.module.rules.push({
      test: /\.mjs$/,
      include: /node_modules/,
      type: 'javascript/auto',
    });

    config.plugins.push(
      new webpack.DefinePlugin({
        antdReproduceVersion: JSON.stringify(version),
      }),
    );

    delete config.module.noParse;

    return config;
  },

  devServerConfig: {
    public: process.env.DEV_HOST || 'localhost',
    disableHostCheck: !!process.env.DEV_HOST,
  },

  htmlTemplateExtraData: {
    isDev,
    usePreact,
  },
};
<commit_msg>:memo: fix site build error<commit_after>const path = require('path');
const replaceLib = require('@ant-design/tools/lib/replaceLib');
const getWebpackConfig = require('@ant-design/tools/lib/getWebpackConfig');
const EsbuildPlugin = require('esbuild-webpack-plugin').default;
const { version } = require('../package.json');
const themeConfig = require('./themeConfig');

const { webpack } = getWebpackConfig;

const isDev = process.env.NODE_ENV === 'development';
const usePreact = process.env.REACT_ENV === 'preact';

function alertBabelConfig(rules) {
  rules.forEach(rule => {
    if (rule.loader && rule.loader === 'babel-loader') {
      if (rule.options.plugins.indexOf(replaceLib) === -1) {
        rule.options.plugins.push(replaceLib);
      }
      rule.options.plugins = rule.options.plugins.filter(
        plugin => !plugin.indexOf || plugin.indexOf('babel-plugin-add-module-exports') === -1,
      );
      // Add babel-plugin-add-react-displayname
      rule.options.plugins.push(require.resolve('babel-plugin-add-react-displayname'));
    } else if (rule.use) {
      alertBabelConfig(rule.use);
    }
  });
}

module.exports = {
  port: 8001,
  hash: true,
  source: {
    components: './components',
    docs: './docs',
    changelog: ['CHANGELOG.zh-CN.md', 'CHANGELOG.en-US.md'],
    'components/form/v3': ['components/form/v3.zh-CN.md', 'components/form/v3.en-US.md'],
    'docs/resources': ['./docs/resources.zh-CN.md', './docs/resources.en-US.md'],
  },
  theme: './site/theme',
  htmlTemplate: './site/theme/static/template.html',
  themeConfig,
  filePathMapper(filePath) {
    if (filePath === '/index.html') {
      return ['/index.html', '/index-cn.html'];
    }
    if (filePath.endsWith('/index.html')) {
      return [filePath, filePath.replace(/\/index\.html$/, '-cn/index.html')];
    }
    if (filePath !== '/404.html' && filePath !== '/index-cn.html') {
      return [filePath, filePath.replace(/\.html$/, '-cn.html')];
    }
    return filePath;
  },
  doraConfig: {
    verbose: true,
  },
  lessConfig: {
    javascriptEnabled: true,
  },
  webpackConfig(config) {
    config.resolve.alias = {
      'antd/lib': path.join(process.cwd(), 'components'),
      'antd/es': path.join(process.cwd(), 'components'),
      antd: path.join(process.cwd(), 'index'),
      site: path.join(process.cwd(), 'site'),
      'react-router': 'react-router/umd/ReactRouter',
    };

    config.externals = {
      'react-router-dom': 'ReactRouterDOM',
    };

    if (usePreact) {
      config.resolve.alias = {
        ...config.resolve.alias,
        react: 'preact-compat',
        'react-dom': 'preact-compat',
        'create-react-class': 'preact-compat/lib/create-react-class',
        'react-router': 'react-router',
      };
    }

    if (isDev) {
      config.devtool = 'source-map';

      // Resolve use react hook fail when yarn link or npm link
      // https://github.com/webpack/webpack/issues/8607#issuecomment-453068938
      config.resolve.alias = { ...config.resolve.alias, react: require.resolve('react') };
    } else if (process.env.ESBUILD) {
      // use esbuild
      config.optimization.minimizer = [new EsbuildPlugin()];
    }

    alertBabelConfig(config.module.rules);

    config.module.rules.push({
      test: /\.mjs$/,
      include: /node_modules/,
      type: 'javascript/auto',
    });

    config.plugins.push(
      new webpack.DefinePlugin({
        antdReproduceVersion: JSON.stringify(version),
      }),
    );

    delete config.module.noParse;

    return config;
  },

  devServerConfig: {
    public: process.env.DEV_HOST || 'localhost',
    disableHostCheck: !!process.env.DEV_HOST,
  },

  htmlTemplateExtraData: {
    isDev,
    usePreact,
  },
};
<|endoftext|>


<commit_before>const assert = require('assert')
const {
  assertNoWarnings,
  assertNoErrors,
  assertErrorMessage,
  assertWarnsCount,
  assertErrorCount
} = require('./../../common/asserts')
const linter = require('./../../../lib/index')
const { funcWith } = require('./../../common/contract-builder')

describe('Linter - reason-string', () => {
  it('should raise reason string is mandatory for require', () => {
    const code = funcWith(`require(!has(role, account));
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['warn', { maxLength: 5 }] }
    })

    assert.ok(report.warningCount > 0)
    assertWarnsCount(report, 1)
    assertErrorMessage(report, 'Provide an error message for require')
  })

  it('should raise reason string is mandatory for revert', () => {
    const code = funcWith(`revert(!has(role, account));
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['error', { maxLength: 5 }] }
    })

    assert.ok(report.errorCount > 0)
    assertErrorCount(report, 1)
    assertErrorMessage(report, 'Provide an error message for revert')
  })

  it('should raise reason string maxLength error for require', () => {
    const code = funcWith(`require(!has(role, account), "Roles: account already has role");
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['warn', { maxLength: 5 }] }
    })

    assert.ok(report.warningCount > 0)
    assertWarnsCount(report, 1)
    assertErrorMessage(report, 'Error message for require is too long')
  })

  it('should raise reason string maxLength error for revert', () => {
    const code = funcWith(`revert(!has(role, account), "Roles: account already has role");
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['error', { maxLength: 5 }] }
    })

    assert.ok(report.errorCount > 0)
    assertErrorCount(report, 1)
    assertErrorMessage(report, 'Error message for revert is too long')
  })

  it('should not raise warning for require', () => {
    const code = funcWith(`require(!has(role, account), "Roles: account already has role");
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['warn', { maxLength: 31 }] }
    })

    assertNoWarnings(report)
    assertNoErrors(report)
  })

  it('should not raise error for revert', () => {
    const code = funcWith(`revert(!has(role, account), "Roles: account already has role");
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['error', { maxLength: 50 }] }
    })

    assertNoWarnings(report)
    assertNoErrors(report)
  })

  it('should not raise warning for require with text errors', () => {
    const code = funcWith(`require
    (!has(role, account),

    "Roles: account already has role");
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['warn', { maxLength: 50 }] }
    })

    assertNoWarnings(report)
    assertNoErrors(report)
  })

  it('should not raise error for revert with text errors', () => {
    const code = funcWith(`     revert
    (!has(role, account),
    "Roles: account already has role");
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['error', { maxLength: 50 }] }
    })

    assertNoWarnings(report)
    assertNoErrors(report)
  })
})
<commit_msg>Remove tests<commit_after>const assert = require('assert')
const {
  assertNoWarnings,
  assertNoErrors,
  assertErrorMessage,
  assertWarnsCount,
  assertErrorCount
} = require('./../../common/asserts')
const linter = require('./../../../lib/index')
const { funcWith } = require('./../../common/contract-builder')

describe('Linter - reason-string', () => {
  it('should raise reason string is mandatory for require', () => {
    const code = funcWith(`require(!has(role, account));
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['warn', { maxLength: 5 }] }
    })

    assert.ok(report.warningCount > 0)
    assertWarnsCount(report, 1)
    assertErrorMessage(report, 'Provide an error message for require')
  })

  it('should raise reason string is mandatory for revert', () => {
    const code = funcWith(`revert(!has(role, account));
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['error', { maxLength: 5 }] }
    })

    assert.ok(report.errorCount > 0)
    assertErrorCount(report, 1)
    assertErrorMessage(report, 'Provide an error message for revert')
  })

  it('should raise reason string maxLength error for require', () => {
    const code = funcWith(`require(!has(role, account), "Roles: account already has role");
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['warn', { maxLength: 5 }] }
    })

    assert.ok(report.warningCount > 0)
    assertWarnsCount(report, 1)
    assertErrorMessage(report, 'Error message for require is too long')
  })

  it('should raise reason string maxLength error for revert', () => {
    const code = funcWith(`revert(!has(role, account), "Roles: account already has role");
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['error', { maxLength: 5 }] }
    })

    assert.ok(report.errorCount > 0)
    assertErrorCount(report, 1)
    assertErrorMessage(report, 'Error message for revert is too long')
  })

  it('should not raise warning for require', () => {
    const code = funcWith(`require(!has(role, account), "Roles: account already has role");
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['warn', { maxLength: 31 }] }
    })

    assertNoWarnings(report)
    assertNoErrors(report)
  })

  it('should not raise error for revert', () => {
    const code = funcWith(`revert(!has(role, account), "Roles: account already has role");
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['error', { maxLength: 50 }] }
    })

    assertNoWarnings(report)
    assertNoErrors(report)
  })
})
<|endoftext|>


<commit_before>QUnit.module('smodules.templateParser', {
  before: function() {
    this.parse = function() {
      this.result = this.parser.parse(this.source);
    };
  },
  beforeEach: function() {
    this.parser = smodules.templateParser();
    this.source = '';
    this.result = null;
  },
});

QUnit.test('normal block - error - 1', function(assert) {
  this.source = '
{left} is ok, only { is forbidden.
';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('normal block - error - 2', function(assert) {
  this.source = '
 } is forbidden.
';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('literal block - error - only open tag', function(assert) {
  this.source = '
{literal}
';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('literal block - error - only close tag', function(assert) {
  this.source = '
{/literal}
';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - no filters - error - space between $ and property name', function(assert) {
  this.source = '{ $ foo } has space between $ and property name.';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - no filters - error - dot between $ and property name', function(assert) {
  this.source = '{ $.foo }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - no filters - error - dot after property name', function(assert) {
  this.source = '{ $foo. }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - no filters - error - continuous dots', function(assert) {
  this.source = '{ $foo..bar }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - filter - error - filter name has space', function(assert) {
  this.source = '{ $foo | invalid filter name }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - filter - error - filter name has symbol', function(assert) {
  this.source = '{ $foo | filter! }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - filter - error - no pipe', function(assert) {
  this.source = '{ $foo pipeNotFound }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - filter - error - no filter args after colon', function(assert) {
  this.source = '{ $foo | filter : }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - filter with args - error - NULL', function(assert) {
  this.source = '{ $foo | filter : NULL }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - filter with args - error - TRUE', function(assert) {
  this.source = '{ $foo | filter : TRUE }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - filter with args - error - FALSE', function(assert) {
  this.source = '{ $foo | filter : FALSE }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - filter with args - string - error - quote 1', function(assert) {
  this.source = '{ $foo | filter : \'test }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - filter with args - string - error - quote 2', function(assert) {
  this.source = '{ $foo | filter : test\' }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - filter with args - string - error - quote char 1', function(assert) {
  this.source = '{ $foo | filter : "test\' }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - filter with args - string - error - quote char 2', function(assert) {
  this.source = '{ $foo | filter : \'test" }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - if elseif else - error 1', function(assert) {
  this.source = '{if $foo}
hoge
';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - if elseif else - error 2', function(assert) {
  this.source = '{elseif $foo}
hoge
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - if elseif else - error 3', function(assert) {
  this.source = '{else}
hoge
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - if elseif else - error 4', function(assert) {
  this.source = '{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});
QUnit.test('if block - if elseif else - error 5', function(assert) {
  this.source = '{if $foo}
foo
{if $bar}
bar
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - roundBracket -> endRoundBracket', function(assert) {
  this.source = '{if () }
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - roundBracket -> comp', function(assert) {
  this.source = '{if ( === $foo )}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - roundBracket -> andor', function(assert) {
  this.source = '{if ( and $foo )}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - endRoundBracket -> roundBracket', function(assert) {
  this.source = '{if ( $foo ) ( === $bar )}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - endRoundBracket -> value', function(assert) {
  this.source = '{if ( $foo ) 10 === $bar}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - endRoundBracket -> var', function(assert) {
  this.source = '{if ( $foo ) $bar gte 10}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - endRoundBracket -> comp', function(assert) {
  this.source = '{if ( $foo ) === $bar}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - value -> roundBracket', function(assert) {
  this.source = '{if 10 ( === $foo )}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - value -> value', function(assert) {
  this.source = '{if 10 20}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - value -> var', function(assert) {
  this.source = '{if 10 $foo}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - var -> roundBracket', function(assert) {
  this.source = '{if $foo ( === $bar )}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - var -> value', function(assert) {
  this.source = '{if $foo 10}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - var -> var', function(assert) {
  this.source = '{if $foo $bar}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - comp -> roundBracket', function(assert) {
  this.source = '{if $foo lte ( $bar ) }
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - comp -> endRoundBracket', function(assert) {
  this.source = '{if ( $foo gte ) $bar}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - comp -> comp', function(assert) {
  this.source = '{if $foo === === $bar}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - comp -> andor', function(assert) {
  this.source = '{if $foo !== or $bar}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - comp -> value -> comp', function(assert) {
  this.source = '{if 10 === 10 === 10}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - comp -> var -> comp', function(assert) {
  this.source = '{if $foo gt $bar gt $baz}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - andor -> endRoundBracket', function(assert) {
  this.source = '{if ( $foo or ) $bar}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - andor -> comp', function(assert) {
  this.source = '{if $foo or === $bar}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - andor -> andor', function(assert) {
  this.source = '{if $foo or or $bar}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - lack of endRoundBracket', function(assert) {
  this.source = '{if ( $foo }
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - lack of roundBracket', function(assert) {
  this.source = '{if ( $foo ) ) }
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('for block - error - lack of index argument', function(assert) {
  this.source = '{for , $item in $items}
{$item}
{/for}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('for block - error - lack of comma', function(assert) {
  this.source = '{for $idx $item in $items}
{$item}
{/for}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('for block - error - lack of value argument', function(assert) {
  this.source = '{for $idx , in $items}
{$idx}
{/for}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('for block - error - lack of in', function(assert) {
  this.source = '{for $idx , $item $items}
{$item}
{/for}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('for block - error - lack of haystack', function(assert) {
  this.source = '{for $idx , $item in}
{$item}
{/for}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('for block - error - too many elements 1', function(assert) {
  this.source = '{for $idx , $item , $foo in $items}
{$item}
{/for}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('for block - error - too many elements 2', function(assert) {

  this.source = '{for $idx , $item in $items1 , $items2}
{$item}
{/for}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('for block - error - too many elements 3', function(assert) {

  this.source = '{for $idx , $item in $items1 $items2}
{$item}
{/for}';

  assert.throws(function() {
    this.parse();
  }, Error);
});
<commit_msg>add and modify tests for if block<commit_after>QUnit.module('smodules.templateParser', {
  before: function() {
    this.parse = function() {
      this.result = this.parser.parse(this.source);
    };
  },
  beforeEach: function() {
    this.parser = smodules.templateParser();
    this.source = '';
    this.result = null;
  },
});

QUnit.test('normal block - error - 1', function(assert) {
  this.source = '
{left} is ok, only { is forbidden.
';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('normal block - error - 2', function(assert) {
  this.source = '
 } is forbidden.
';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('literal block - error - only open tag', function(assert) {
  this.source = '
{literal}
';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('literal block - error - only close tag', function(assert) {
  this.source = '
{/literal}
';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - no filters - error - space between $ and property name', function(assert) {
  this.source = '{ $ foo } has space between $ and property name.';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - no filters - error - dot between $ and property name', function(assert) {
  this.source = '{ $.foo }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - no filters - error - dot after property name', function(assert) {
  this.source = '{ $foo. }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - no filters - error - continuous dots', function(assert) {
  this.source = '{ $foo..bar }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - filter - error - filter name has space', function(assert) {
  this.source = '{ $foo | invalid filter name }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - filter - error - filter name has symbol', function(assert) {
  this.source = '{ $foo | filter! }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - filter - error - no pipe', function(assert) {
  this.source = '{ $foo pipeNotFound }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - filter - error - no filter args after colon', function(assert) {
  this.source = '{ $foo | filter : }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - filter with args - error - NULL', function(assert) {
  this.source = '{ $foo | filter : NULL }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - filter with args - error - TRUE', function(assert) {
  this.source = '{ $foo | filter : TRUE }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - filter with args - error - FALSE', function(assert) {
  this.source = '{ $foo | filter : FALSE }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - filter with args - string - error - quote 1', function(assert) {
  this.source = '{ $foo | filter : \'test }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - filter with args - string - error - quote 2', function(assert) {
  this.source = '{ $foo | filter : test\' }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - filter with args - string - error - quote char 1', function(assert) {
  this.source = '{ $foo | filter : "test\' }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('holder block - filter with args - string - error - quote char 2', function(assert) {
  this.source = '{ $foo | filter : \'test" }';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - if elseif else - error 1', function(assert) {
  this.source = '{if $foo}
hoge
';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - if elseif else - error 2', function(assert) {
  this.source = '{elseif $foo}
hoge
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - if elseif else - error 3', function(assert) {
  this.source = '{else}
hoge
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - if elseif else - error 4', function(assert) {
  this.source = '{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});
QUnit.test('if block - if elseif else - error 5', function(assert) {
  this.source = '{if $foo}
foo
{if $bar}
bar
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - no space', function(assert) {
  this.source = '{if$foo }
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - roundBracket -> endRoundBracket', function(assert) {
  this.source = '{if () }
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - roundBracket -> comp', function(assert) {
  this.source = '{if ( === $foo )}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - roundBracket -> andor', function(assert) {
  this.source = '{if ( and $foo )}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - endRoundBracket -> roundBracket', function(assert) {
  this.source = '{if ( $foo ) ( $bar )}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - endRoundBracket -> value', function(assert) {
  this.source = '{if ( $foo ) 10 === $bar}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - endRoundBracket -> var', function(assert) {
  this.source = '{if ( $foo ) $bar gte 10}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - endRoundBracket -> comp', function(assert) {
  this.source = '{if ( $foo ) === $bar}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - value -> roundBracket', function(assert) {
  this.source = '{if 10 ( === $foo )}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - value -> value', function(assert) {
  this.source = '{if 10 20}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - value -> var', function(assert) {
  this.source = '{if 10 $foo}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - var -> roundBracket', function(assert) {
  this.source = '{if $foo ( === $bar )}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - var -> value', function(assert) {
  this.source = '{if $foo 10}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - var -> var', function(assert) {
  this.source = '{if $foo $bar}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - comp -> roundBracket', function(assert) {
  this.source = '{if $foo lte ( $bar ) }
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - comp -> endRoundBracket', function(assert) {
  this.source = '{if ( $foo gte ) $bar}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - comp -> comp', function(assert) {
  this.source = '{if $foo === === $bar}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - comp -> andor', function(assert) {
  this.source = '{if $foo !== or $bar}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - comp -> value -> comp', function(assert) {
  this.source = '{if 10 === 10 === 10}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - comp -> var -> comp', function(assert) {
  this.source = '{if $foo gt $bar gt $baz}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - andor -> endRoundBracket', function(assert) {
  this.source = '{if ( $foo or ) $bar}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - andor -> comp', function(assert) {
  this.source = '{if $foo or === $bar}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - andor -> andor', function(assert) {
  this.source = '{if $foo or or $bar}
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - lack of endRoundBracket', function(assert) {
  this.source = '{if ( $foo }
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('if block - conditions - error - lack of roundBracket', function(assert) {
  this.source = '{if ( $foo ) ) }
ok
{/if}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('for block - error - lack of index argument', function(assert) {
  this.source = '{for , $item in $items}
{$item}
{/for}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('for block - error - lack of comma', function(assert) {
  this.source = '{for $idx $item in $items}
{$item}
{/for}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('for block - error - lack of value argument', function(assert) {
  this.source = '{for $idx , in $items}
{$idx}
{/for}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('for block - error - lack of in', function(assert) {
  this.source = '{for $idx , $item $items}
{$item}
{/for}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('for block - error - lack of haystack', function(assert) {
  this.source = '{for $idx , $item in}
{$item}
{/for}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('for block - error - too many elements 1', function(assert) {
  this.source = '{for $idx , $item , $foo in $items}
{$item}
{/for}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('for block - error - too many elements 2', function(assert) {

  this.source = '{for $idx , $item in $items1 , $items2}
{$item}
{/for}';

  assert.throws(function() {
    this.parse();
  }, Error);
});

QUnit.test('for block - error - too many elements 3', function(assert) {

  this.source = '{for $idx , $item in $items1 $items2}
{$item}
{/for}';

  assert.throws(function() {
    this.parse();
  }, Error);
});
<|endoftext|>


<commit_before>import SettingsStore from '../stores/settings-store.js';
import { isMockMode } from '../util/env.js';

export function prefixImage(url) {
  if(isMockMode) {

    const decoded = decodeURIComponent(url.replace('image://', ''));
    return decoded.substr(0, decoded.length - 1);

  } else {

    const settings = SettingsStore.get();
    const address = `http://${settings.ipAddress}:${settings.ajaxPort}/`;

    return `${address}image/${encodeURIComponent(url)}`;

  }
}
<commit_msg>use https for images in demo mode<commit_after>import SettingsStore from '../stores/settings-store.js';
import { isMockMode } from '../util/env.js';

export function prefixImage(url) {
  if(isMockMode) {

    const decoded = decodeURIComponent(url.replace('image://', ''));
    return decoded.substr(0, decoded.length - 1).replace('http://', 'https://');

  } else {

    const settings = SettingsStore.get();
    const address = `http://${settings.ipAddress}:${settings.ajaxPort}/`;

    return `${address}image/${encodeURIComponent(url)}`;

  }
}
<|endoftext|>


<commit_before>import { DISCOVER, FOLLOWING, STARRED } from '../../../src/constants/locales/en.js'
import {
  selectBroadcast,
  selectHasCoverProfile,
  selectHasPromotion,
} from '../../../src/containers/HeroContainer'

describe('HeroContainer', () => {
  context('#selectHasPromotion', () => {
    it('selects with memoization whether the current route has a promotion', () => {
      let state = { routing: { location: { pathname: '/search', change: false } } }
      expect(selectHasPromotion(state)).to.equal(true)

      state = { routing: { location: { pathname: '/search', change: true } } }
      expect(selectHasPromotion(state)).to.equal(true)
      expect(selectHasPromotion.recomputations()).to.equal(1)

      state = { routing: { location: { pathname: '/discover', change: true } } }
      expect(selectHasPromotion(state)).to.equal(true)
      expect(selectHasPromotion.recomputations()).to.equal(2)

      state = { routing: { location: { pathname: '/discover/stuff', change: true } } }
      expect(selectHasPromotion(state)).to.equal(true)

      state = { routing: { location: { pathname: '/', change: true } } }
      expect(selectHasPromotion(state)).to.equal(true)

      const noPromos = [
        '/following',
        '/starred',
        '/settings',
        '/invitations',
        '/notifications',
        '/mk/post/etlb9br06dh6tleztw4g',
        '/mk',
        '/mk/loves',
        '/mk/following',
        '/mk/followers',
      ]
      noPromos.forEach((route) => {
        state = { routing: { location: { pathname: route, change: false } } }
        expect(selectHasPromotion(state)).to.equal(false, `${route} should not have a Promotion.`)
      })
      expect(selectHasPromotion.recomputations()).to.equal(14)
    })
  })

  context('#selectHasCoverProfile', () => {
    it('selects with memoization whether the current route has a cover', () => {
      let state = { routing: { location: { pathname: '/mk', change: false } } }
      const props = { params: { username: 'mk' } }
      expect(selectHasCoverProfile(state, props)).to.equal(true)

      state = { routing: { location: { pathname: '/mk/loves', change: true } } }
      expect(selectHasCoverProfile(state, props)).to.equal(true)
      expect(selectHasCoverProfile.recomputations()).to.equal(1)

      state = { routing: { location: { pathname: '/mk/following', change: true } } }
      expect(selectHasCoverProfile(state, props)).to.equal(true)
      expect(selectHasCoverProfile.recomputations()).to.equal(1)

      state = { routing: { location: { pathname: '/mk/followers', change: true } } }
      expect(selectHasCoverProfile(state, props)).to.equal(true)

      state = { routing: { location: { pathname: '/mk/posts', change: true } } }
      expect(selectHasCoverProfile(state, props)).to.equal(true)

      const noCovers = [
        '/',
        '/following',
        '/starred',
        '/settings',
        '/invitations',
        '/notifications',
        '/mk/post/etlb9br06dh6tleztw4g',
      ]
      noCovers.forEach((route) => {
        state = { routing: { location: { pathname: route, change: false } } }
        expect(selectHasCoverProfile(state)).to.equal(false, `${route} should not have a Cover.`)
      })
      expect(selectHasCoverProfile.recomputations()).to.equal(8)
    })
  })

  context('#selectBroadcast', () => {
    it('selects with memoization whether the current route has a broadcast message', () => {
      let state = {
        gui: { lastDiscoverBeaconVersion: null },
        routing: { location: { pathname: '/discover', change: false } },
      }
      expect(selectBroadcast(state)).to.equal(DISCOVER.BEACON_TEXT)

      state = {
        gui: { lastDiscoverBeaconVersion: null },
        routing: { location: { pathname: '/discover', change: true } },
      }
      expect(selectBroadcast(state)).to.equal(DISCOVER.BEACON_TEXT)
      expect(selectBroadcast.recomputations()).to.equal(1)

      state = {
        gui: { lastDiscoverBeaconVersion: '1' },
        routing: { location: { pathname: '/discover', change: true } },
      }
      expect(selectBroadcast(state)).to.equal(null)

      state = {
        gui: { lastFollowingBeaconVersion: null },
        routing: { location: { pathname: '/following', change: true } },
      }
      expect(selectBroadcast(state)).to.equal(FOLLOWING.BEACON_TEXT)

      state = {
        gui: { lastFollowingBeaconVersion: '1' },
        routing: { location: { pathname: '/following', change: true } },
      }
      expect(selectBroadcast(state)).to.equal(null)

      state = {
        gui: { lastStarredBeaconVersion: null },
        routing: { location: { pathname: '/starred', change: true } },
      }
      expect(selectBroadcast(state)).to.equal(STARRED.BEACON_TEXT)

      state = {
        gui: { lastStarredBeaconVersion: '1' },
        routing: { location: { pathname: '/starred', change: true } },
      }
      expect(selectBroadcast(state)).to.equal(null)

      const noBroadcasts = [
        '/settings',
        '/invitations',
        '/notifications',
        '/mk/post/etlb9br06dh6tleztw4g',
        '/mk',
        '/mk/loves',
        '/mk/following',
        '/mk/followers',
      ]
      noBroadcasts.forEach((route) => {
        state = { routing: { location: { pathname: route, change: false } } }
        expect(selectBroadcast(state)).to.equal(null, `${route} should not have a Broadcast.`)
      })
      expect(selectBroadcast.recomputations()).to.equal(11)
    })
  })
})

<commit_msg>Fix a test with finding beacons in hero<commit_after>import { DISCOVER, FOLLOWING, STARRED } from '../../../src/constants/locales/en.js'
import {
  selectBroadcast,
  selectHasCoverProfile,
  selectHasPromotion,
} from '../../../src/containers/HeroContainer'

describe('HeroContainer', () => {
  context('#selectHasPromotion', () => {
    it('selects with memoization whether the current route has a promotion', () => {
      let state = { routing: { location: { pathname: '/search', change: false } } }
      expect(selectHasPromotion(state)).to.equal(true)

      state = { routing: { location: { pathname: '/search', change: true } } }
      expect(selectHasPromotion(state)).to.equal(true)
      expect(selectHasPromotion.recomputations()).to.equal(1)

      state = { routing: { location: { pathname: '/discover', change: true } } }
      expect(selectHasPromotion(state)).to.equal(true)
      expect(selectHasPromotion.recomputations()).to.equal(2)

      state = { routing: { location: { pathname: '/discover/stuff', change: true } } }
      expect(selectHasPromotion(state)).to.equal(true)

      state = { routing: { location: { pathname: '/', change: true } } }
      expect(selectHasPromotion(state)).to.equal(true)

      const noPromos = [
        '/following',
        '/starred',
        '/settings',
        '/invitations',
        '/notifications',
        '/mk/post/etlb9br06dh6tleztw4g',
        '/mk',
        '/mk/loves',
        '/mk/following',
        '/mk/followers',
      ]
      noPromos.forEach((route) => {
        state = { routing: { location: { pathname: route, change: false } } }
        expect(selectHasPromotion(state)).to.equal(false, `${route} should not have a Promotion.`)
      })
      expect(selectHasPromotion.recomputations()).to.equal(14)
    })
  })

  context('#selectHasCoverProfile', () => {
    it('selects with memoization whether the current route has a cover', () => {
      let state = { routing: { location: { pathname: '/mk', change: false } } }
      const props = { params: { username: 'mk' } }
      expect(selectHasCoverProfile(state, props)).to.equal(true)

      state = { routing: { location: { pathname: '/mk/loves', change: true } } }
      expect(selectHasCoverProfile(state, props)).to.equal(true)
      expect(selectHasCoverProfile.recomputations()).to.equal(1)

      state = { routing: { location: { pathname: '/mk/following', change: true } } }
      expect(selectHasCoverProfile(state, props)).to.equal(true)
      expect(selectHasCoverProfile.recomputations()).to.equal(1)

      state = { routing: { location: { pathname: '/mk/followers', change: true } } }
      expect(selectHasCoverProfile(state, props)).to.equal(true)

      state = { routing: { location: { pathname: '/mk/posts', change: true } } }
      expect(selectHasCoverProfile(state, props)).to.equal(true)

      const noCovers = [
        '/',
        '/following',
        '/starred',
        '/settings',
        '/invitations',
        '/notifications',
        '/mk/post/etlb9br06dh6tleztw4g',
      ]
      noCovers.forEach((route) => {
        state = { routing: { location: { pathname: route, change: false } } }
        expect(selectHasCoverProfile(state)).to.equal(false, `${route} should not have a Cover.`)
      })
      expect(selectHasCoverProfile.recomputations()).to.equal(8)
    })
  })

  context('#selectBroadcast', () => {
    it('selects with memoization whether the current route has a broadcast message', () => {
      let state = {
        authentication: { isLoggedIn: true },
        gui: { lastDiscoverBeaconVersion: null },
        routing: { location: { pathname: '/discover', change: false } },
      }
      expect(selectBroadcast(state)).to.equal(DISCOVER.BEACON_TEXT)

      state = {
        authentication: { isLoggedIn: true },
        gui: { lastDiscoverBeaconVersion: null },
        routing: { location: { pathname: '/discover', change: true } },
      }
      expect(selectBroadcast(state)).to.equal(DISCOVER.BEACON_TEXT)
      expect(selectBroadcast.recomputations()).to.equal(1)

      state = {
        authentication: { isLoggedIn: true },
        gui: { lastDiscoverBeaconVersion: '1' },
        routing: { location: { pathname: '/discover', change: true } },
      }
      expect(selectBroadcast(state)).to.equal(null)

      state = {
        authentication: { isLoggedIn: true },
        gui: { lastFollowingBeaconVersion: null },
        routing: { location: { pathname: '/following', change: true } },
      }
      expect(selectBroadcast(state)).to.equal(FOLLOWING.BEACON_TEXT)

      state = {
        authentication: { isLoggedIn: true },
        gui: { lastFollowingBeaconVersion: '1' },
        routing: { location: { pathname: '/following', change: true } },
      }
      expect(selectBroadcast(state)).to.equal(null)

      state = {
        authentication: { isLoggedIn: true },
        gui: { lastStarredBeaconVersion: null },
        routing: { location: { pathname: '/starred', change: true } },
      }
      expect(selectBroadcast(state)).to.equal(STARRED.BEACON_TEXT)

      state = {
        authentication: { isLoggedIn: true },
        gui: { lastStarredBeaconVersion: '1' },
        routing: { location: { pathname: '/starred', change: true } },
      }
      expect(selectBroadcast(state)).to.equal(null)

      const noBroadcasts = [
        '/settings',
        '/invitations',
        '/notifications',
        '/mk/post/etlb9br06dh6tleztw4g',
        '/mk',
        '/mk/loves',
        '/mk/following',
        '/mk/followers',
      ]
      noBroadcasts.forEach((route) => {
        state = { routing: { location: { pathname: route, change: false } } }
        expect(selectBroadcast(state)).to.equal(null, `${route} should not have a Broadcast.`)
      })
      expect(selectBroadcast.recomputations()).to.equal(11)
    })
  })
})

<|endoftext|>


<commit_before>[
 {
     "_id": { "$oid": "56b8f05cf9fcee1b00000010" },
     "title": "sada",
     "body": "body3",
     "user_id": "56b8d7caf9fcee1b00000001",
     "updated_at": { "$date" : "2016-02-08T20:02:12.985Z"},
     "created_at": { "$date" : "2016-02-08T19:45:32.501Z"}
 },
 {
     "_id": { "$oid": "56b8da59f9fcee1b00000007" },
     "updated_at" : { "$date" :"2016-02-08T19:52:23.883Z"},
     "comments": [
        {
          "_id": { "$oid": "56b8f344f9fcee1b00000018" },
          "updated_at": "2016-02-08T19:57:56.678Z",
          "created_at": "2016-02-08T19:57:56.678Z"
        },
        {
          "_id": { "$oid": "56b8f35ef9fcee1b0000001a" },
          "updated_at": "2016-02-08T19:58:22.847Z",
          "created_at": "2016-02-08T19:58:22.847Z"
        }
     ]
 }
]<commit_msg>fied test data<commit_after>[
 {
     "_id": { "$oid": "56b8f05cf9fcee1b00000010" },
     "title": "sada",
     "body": "body3",
     "user_id": "56b8d7caf9fcee1b00000001",
     "updated_at": { "$date" : "2016-02-08T20:02:12.985Z"},
     "created_at": { "$date" : "2016-02-08T19:45:32.501Z"}
 },
 {
     "_id": { "$oid": "56b8da59f9fcee1b00000007" },
     "updated_at" : { "$date" :"2016-02-08T19:52:23.883Z"},
     "comments": [
        {
          "_id": { "$oid": "56b8f344f9fcee1b00000018" },
          "updated_at": {"$date":"2016-02-08T19:57:56.678Z"},
          "created_at": {"$date":"2016-02-08T19:57:56.678Z"}
        },
        {
          "_id": { "$oid": "56b8f35ef9fcee1b0000001a" },
          "updated_at": {"$date":"2016-02-08T19:58:22.847Z"},
          "created_at": {"$date":"2016-02-08T19:58:22.847Z"}
        }
     ]
 }
]
<|endoftext|>