Mailspring/spec/components/editable-list-spec.jsx
Ben Gotow af67597f0b feat(mail-rules): Per-account mail rules filter incoming, existing mail
Summary:
Originally, this was going to be a totally independent package, but
I wasn't able to isolate the functionality and get it tied in to
the delta-stream consumption. Here's how it currently works:

- The preferences package has a new tab which allows you to edit
  mail filters. Filters are saved in a new core store, and a new
  stock component (ScenarioEditor) renders the editor. The editor
  takes a set of templates that define a value space, and outputs
  a valid set of values.

- A new MailFilterProcessor takes messages and creates tasks to
  apply the actions from the MailFiltersStore.

- The worker-sync package now uses the MailFilterProcessor to
  apply filters /before/ it calls didPassivelyReceiveNewModels,
  so filtrs are applied before any notifications are created.

- A new task, ReprocessMailFiltersTask allows you to run filters
  on all of your existing mail. It leverages the existing TaskQueue
  architecture to: a) resume where it left off if you quit midway,
  b) be queryable (for status) from all windows and c) cancelable.
  The TaskQueue is a bit strange because it runs performLocal and
  performRemote very differently, and I had to use `performRemote`.
  (todo refactor soon.)

This diff also changes the EditableList a bit to behave like a
controlled component and render focused / unfocused states.

Test Plan: Run tests, only for actual filter processing atm.

Reviewers: juan, evan

Reviewed By: evan

Differential Revision: https://phab.nylas.com/D2379
2015-12-23 02:19:32 -05:00

240 lines
8.7 KiB
JavaScript

import React, {addons} from 'react/addons';
import EditableList from '../../src/components/editable-list';
import {renderIntoDocument} from '../nylas-test-utils'
const {findDOMNode} = React;
const {TestUtils: {
findRenderedDOMComponentWithTag,
findRenderedDOMComponentWithClass,
scryRenderedDOMComponentsWithClass,
Simulate,
}} = addons;
const makeList = (items = [], props = {})=> {
return renderIntoDocument(<EditableList {...props} items={items}></EditableList>);
};
describe('EditableList', ()=> {
describe('_onItemClick', ()=> {
it('calls onSelectItem', ()=> {
const onSelectItem = jasmine.createSpy('onSelectItem');
const list = makeList(['1', '2'], {onSelectItem});
const item = scryRenderedDOMComponentsWithClass(list, 'editable-item')[0];
Simulate.click(item);
expect(onSelectItem).toHaveBeenCalledWith('1', 0);
});
});
describe('_onItemEdit', ()=> {
it('enters editing mode when double click', ()=> {
const list = makeList(['1', '2']);
spyOn(list, 'setState');
const item = scryRenderedDOMComponentsWithClass(list, 'editable-item')[0];
Simulate.doubleClick(item);
expect(list.setState).toHaveBeenCalledWith({editingIndex: 0});
});
it('enters editing mode when edit icon clicked', ()=> {
const list = makeList(['1', '2']);
spyOn(list, 'setState');
const editIcon = scryRenderedDOMComponentsWithClass(list, 'edit-icon')[0];
Simulate.click(editIcon);
expect(list.setState).toHaveBeenCalledWith({editingIndex: 0});
});
});
describe('core:previous-item / core:next-item', ()=> {
it('calls onSelectItem', ()=> {
const onSelectItem = jasmine.createSpy('onSelectItem');
const list = makeList(['1', '2'], {selected: '1', onSelectItem});
const innerList = findRenderedDOMComponentWithClass(list, 'items-wrapper');
NylasEnv.commands.dispatch(React.findDOMNode(innerList), 'core:next-item');
expect(onSelectItem).toHaveBeenCalledWith('2', 1);
});
it('does not select an item when at the bottom of the list and moves down', ()=> {
const onSelectItem = jasmine.createSpy('onSelectItem');
const list = makeList(['1', '2'], {selected: '2', onSelectItem});
const innerList = findRenderedDOMComponentWithClass(list, 'items-wrapper');
NylasEnv.commands.dispatch(React.findDOMNode(innerList), 'core:next-item');
expect(onSelectItem).not.toHaveBeenCalled();
});
it('does not select an item when at the top of the list and moves up', ()=> {
const onSelectItem = jasmine.createSpy('onSelectItem');
const list = makeList(['1', '2'], {selected: '1', onSelectItem});
const innerList = findRenderedDOMComponentWithClass(list, 'items-wrapper');
NylasEnv.commands.dispatch(React.findDOMNode(innerList), 'core:previous-item');
expect(onSelectItem).not.toHaveBeenCalled();
});
it('does not clear the selection when esc pressed but prop does not allow it', ()=> {
const onSelectItem = jasmine.createSpy('onSelectItem');
const list = makeList(['1', '2'], {selected: '1', allowEmptySelection: false, onSelectItem});
const innerList = findRenderedDOMComponentWithClass(list, 'items-wrapper');
Simulate.keyDown(innerList, {key: 'Escape'});
expect(onSelectItem).not.toHaveBeenCalled();
});
});
describe('_onCreateInputKeyDown', ()=> {
it('calls onItemCreated', ()=> {
const onItemCreated = jasmine.createSpy('onItemCreated');
const list = makeList(['1', '2'], {initialState: {creatingItem: true}, onItemCreated});
const createItem = findRenderedDOMComponentWithClass(list, 'create-item-input');
const input = findRenderedDOMComponentWithTag(createItem, 'input');
findDOMNode(input).value = 'New Item';
Simulate.keyDown(input, {key: 'Enter'});
expect(onItemCreated).toHaveBeenCalledWith('New Item');
});
});
describe('_onCreateItem', ()=> {
it('should call prop callback when provided', ()=> {
const onCreateItem = jasmine.createSpy('onCreateItem');
const list = makeList(['1', '2'], {onCreateItem});
list._onCreateItem();
expect(onCreateItem).toHaveBeenCalled();
});
it('should set state for creating item when no callback provided', ()=> {
const list = makeList(['1', '2']);
spyOn(list, 'setState');
list._onCreateItem();
expect(list.setState).toHaveBeenCalledWith({creatingItem: true});
});
});
describe('_renderItem', ()=> {
const makeItem = (item, idx, state = {}, handlers = {})=> {
const list = makeList([], {initialState: state});
return renderIntoDocument(
list._renderItem(item, idx, state, handlers)
);
};
it('binds correct click callbacks', ()=> {
const onClick = jasmine.createSpy('onClick');
const onEdit = jasmine.createSpy('onEdit');
const item = makeItem('item 1', 0, {}, {onClick, onEdit});
Simulate.click(item);
expect(onClick.calls[0].args[1]).toEqual('item 1');
expect(onClick.calls[0].args[2]).toEqual(0);
Simulate.doubleClick(item);
expect(onEdit.calls[0].args[1]).toEqual('item 1');
expect(onEdit.calls[0].args[2]).toEqual(0);
});
it('renders correctly when item is selected', ()=> {
const item = findDOMNode(makeItem('item 1', 0, {selected: 'item 1'}));
expect(item.className.indexOf('selected')).not.toEqual(-1);
});
it('renders correctly when item is string', ()=> {
const item = findDOMNode(makeItem('item 1', 0));
expect(item.className.indexOf('selected')).toEqual(-1);
expect(item.className.indexOf('editable-item')).not.toEqual(-1);
expect(item.childNodes[0].textContent).toEqual('item 1');
});
it('renders correctly when item is component', ()=> {
const item = findDOMNode(makeItem(<div></div>, 0));
expect(item.className.indexOf('selected')).toEqual(-1);
expect(item.className.indexOf('editable-item')).toEqual(-1);
expect(item.childNodes[0].tagName).toEqual('DIV');
});
it('renders correctly when item is in editing state', ()=> {
const onInputBlur = jasmine.createSpy('onInputBlur');
const onInputFocus = jasmine.createSpy('onInputFocus');
const onInputKeyDown = jasmine.createSpy('onInputKeyDown');
const item = makeItem('item 1', 0, {editingIndex: 0}, {onInputBlur, onInputFocus, onInputKeyDown});
const input = findRenderedDOMComponentWithTag(item, 'input');
Simulate.focus(input);
Simulate.keyDown(input);
Simulate.blur(input);
expect(onInputFocus).toHaveBeenCalled();
expect(onInputBlur).toHaveBeenCalled();
expect(onInputKeyDown.calls[0].args[1]).toEqual('item 1');
expect(onInputKeyDown.calls[0].args[2]).toEqual(0);
expect(findDOMNode(input).tagName).toEqual('INPUT');
});
});
describe('render', ()=> {
it('renders list of items', ()=> {
const items = ['1', '2', '3'];
const list = makeList(items);
const innerList = findDOMNode(
findRenderedDOMComponentWithClass(list, 'scroll-region-content-inner')
);
expect(()=> {
findRenderedDOMComponentWithClass(list, 'create-item-input');
}).toThrow();
expect(innerList.childNodes.length).toEqual(3);
items.forEach((item, idx)=> expect(innerList.childNodes[idx].textContent).toEqual(item));
});
it('renders create input as an item when creating', ()=> {
const items = ['1', '2', '3'];
const list = makeList(items, {initialState: {creatingItem: true}});
const createItem = findRenderedDOMComponentWithClass(list, 'create-item-input');
expect(createItem).toBeDefined();
});
it('renders add button', ()=> {
const list = makeList();
const button = scryRenderedDOMComponentsWithClass(list, 'btn-editable-list')[0];
expect(findDOMNode(button).textContent).toEqual('+');
});
it('renders delete button', ()=> {
const onSelectItem = jasmine.createSpy('onSelectItem');
const onDeleteItem = jasmine.createSpy('onDeleteItem');
const list = makeList(['1', '2'], {selected: '2', onDeleteItem, onSelectItem});
const button = scryRenderedDOMComponentsWithClass(list, 'btn-editable-list')[1];
Simulate.click(button);
expect(findDOMNode(button).textContent).toEqual('—');
expect(onDeleteItem).toHaveBeenCalledWith('2', 1);
});
it('disables teh delete button when no item is selected', ()=> {
const onSelectItem = jasmine.createSpy('onSelectItem');
const onDeleteItem = jasmine.createSpy('onDeleteItem');
const list = makeList(['1', '2'], {selected: null, onDeleteItem, onSelectItem});
const button = scryRenderedDOMComponentsWithClass(list, 'btn-editable-list')[1];
Simulate.click(button);
expect(onDeleteItem).not.toHaveBeenCalledWith('2', 1);
});
});
});