<commit_before>import React, { Component } from 'react';

export default class Truncate extends Component {
    static propTypes = {
        children: React.PropTypes.node,
        ellipsis: React.PropTypes.node,
        lines: React.PropTypes.number
    };

    static defaultProps = {
        children: '',
        ellipsis: '…',
        lines: 1
    };

    componentDidMount() {
        let canvas = document.createElement('canvas');
        this.canvas = canvas.getContext('2d');

        window.addEventListener('resize', this.onResize);

        this.onResize();
    }

    componentWillUnmount() {
        window.removeEventListener('resize', this.onResize);
    }

    onResize = () => {
        this.setState({
            targetWidth: null
        }, this.calcTargetWidth);
    };

    calcTargetWidth = () => {
        // Calculation is no longer relevant, since node has been removed
        if (!this.refs.target) {
            return;
        }

        // Delay calculation until parent node is inserted to the document
        // Mounting order in React is ChildComponent, ParentComponent
        if (!this.refs.target.parentNode.getBoundingClientRect().width) {
            return requestAnimationFrame(this.calcTargetWidth);
        }

        this.refs.target.style.display = 'inline-block';
        this.refs.target.style.width = '100%';
        this.refs.target.style.whiteSpace = null;

        let style = window.getComputedStyle(this.refs.target);
        let targetWidth = this.refs.target.clientWidth;

        this.refs.target.style.display = null;
        this.refs.target.style.width = null;
        this.refs.target.style.whiteSpace = 'nowrap';

        let font = [
            style['font-weight'],
            style['font-style'],
            style['font-size'],
            style['font-family']
        ].join(' ');

        this.canvas.font = font;

        this.setState({
            targetWidth
        });
    };

    measureWidth = text => {
        return this.canvas.measureText(text).width;
    };

    getLines() {
        let {
            refs: {
                text: {
                    textContent: text
                },
                ellipsis: {
                    textContent: ellipsisText
                }
            },
            props: {
                lines: numLines,
                ellipsis
            },
            state: {
                targetWidth
            },
            measureWidth
        } = this;

        let lines = [];
        let textWords = text.split(' ');

        for (let line = 1; line <= numLines; line++) {
            let resultLine = textWords.join(' ');

            if (measureWidth(resultLine) < targetWidth) {
                // Line is end of text and fits without truncating //
                lines.push(resultLine);
                break;
            }

            if (line === numLines) {
                // Binary search determining the longest possible line inluding truncate string //
                let textRest = textWords.join(' ');

                let lower = 0;
                let upper = textRest.length - 1;

                let testLine;

                while (lower <= upper) {
                    let middle = Math.floor((lower + upper) / 2);

                    testLine = textRest.slice(0, middle);

                    if (measureWidth(testLine + ellipsisText) <= targetWidth) {
                        resultLine = {testLine}{ellipsis};

                        lower = middle + 1;
                    } else {
                        upper = middle - 1;
                    }
                }
            } else {
                // Binary search determining when the line breaks //
                let lower = 0;
                let upper = textWords.length - 1;

                let testLine;
                let resultMiddle;

                while (lower <= upper) {
                    let middle = Math.floor((lower + upper) / 2);

                    testLine = textWords.slice(0, middle).join(' ');

                    if (measureWidth(testLine) <= targetWidth) {
                        resultLine = testLine;
                        resultMiddle = middle;

                        lower = middle + 1;
                    } else {
                        upper = middle - 1;
                    }
                }

                textWords = textWords.slice(resultMiddle, textWords.length);
            }

            lines.push(resultLine);
        }

        return lines;
    }

    renderLine = (line, i, arr) => {
        if (i === arr.length - 1) {
            return {line};
        } else {
            return [
                {line},

            ];
        }
    };

    render() {
        let {
            refs: {
                target
            },
            props,
            props: {
                children,
                ellipsis,
                lines
            }
        } = this;

        let text = children;

        if (target && lines > 0) {
            text = this.getLines().map(this.renderLine);
        }

        return (

                {text}

                    {children}
                    {ellipsis}

        );
    }

    styles = {
        raw: {
            display: 'none'
        }
    };
};
<commit_msg>Reset target styles so that toggling between truncating and not truncating works seamlessly<commit_after>import React, { Component } from 'react';

export default class Truncate extends Component {
    static propTypes = {
        children: React.PropTypes.node,
        ellipsis: React.PropTypes.node,
        lines: React.PropTypes.number
    };

    static defaultProps = {
        children: '',
        ellipsis: '…',
        lines: 1
    };

    componentDidMount() {
        let canvas = document.createElement('canvas');
        this.canvas = canvas.getContext('2d');

        window.addEventListener('resize', this.onResize);

        this.onResize();
    }

    componentWillUnmount() {
        window.removeEventListener('resize', this.onResize);
    }

    onResize = () => {
        this.setState({
            targetWidth: null
        }, this.calcTargetWidth);
    };

    calcTargetWidth = () => {
        // Calculation is no longer relevant, since node has been removed
        if (!this.refs.target) {
            return;
        }

        // Delay calculation until parent node is inserted to the document
        // Mounting order in React is ChildComponent, ParentComponent
        if (!this.refs.target.parentNode.getBoundingClientRect().width) {
            return requestAnimationFrame(this.calcTargetWidth);
        }

        this.refs.target.style.display = 'inline-block';
        this.refs.target.style.width = '100%';
        this.refs.target.style.whiteSpace = null;

        let style = window.getComputedStyle(this.refs.target);
        let targetWidth = this.refs.target.clientWidth;

        this.refs.target.style.display = null;
        this.refs.target.style.width = null;
        this.refs.target.style.whiteSpace = 'nowrap';

        let font = [
            style['font-weight'],
            style['font-style'],
            style['font-size'],
            style['font-family']
        ].join(' ');

        this.canvas.font = font;

        this.setState({
            targetWidth
        });
    };

    measureWidth = text => {
        return this.canvas.measureText(text).width;
    };

    resetTargetStyle = () => {
        this.refs.target.style.display = null;
        this.refs.target.style.width = null;
        this.refs.target.style.whiteSpace = null;
    };

    getLines() {
        let {
            refs: {
                text: {
                    textContent: text
                },
                ellipsis: {
                    textContent: ellipsisText
                }
            },
            props: {
                lines: numLines,
                ellipsis
            },
            state: {
                targetWidth
            },
            measureWidth
        } = this;

        let lines = [];
        let textWords = text.split(' ');

        for (let line = 1; line <= numLines; line++) {
            let resultLine = textWords.join(' ');

            if (measureWidth(resultLine) < targetWidth) {
                // Line is end of text and fits without truncating //
                lines.push(resultLine);
                break;
            }

            if (line === numLines) {
                // Binary search determining the longest possible line inluding truncate string //
                let textRest = textWords.join(' ');

                let lower = 0;
                let upper = textRest.length - 1;

                let testLine;

                while (lower <= upper) {
                    let middle = Math.floor((lower + upper) / 2);

                    testLine = textRest.slice(0, middle);

                    if (measureWidth(testLine + ellipsisText) <= targetWidth) {
                        resultLine = {testLine}{ellipsis};

                        lower = middle + 1;
                    } else {
                        upper = middle - 1;
                    }
                }
            } else {
                // Binary search determining when the line breaks //
                let lower = 0;
                let upper = textWords.length - 1;

                let testLine;
                let resultMiddle;

                while (lower <= upper) {
                    let middle = Math.floor((lower + upper) / 2);

                    testLine = textWords.slice(0, middle).join(' ');

                    if (measureWidth(testLine) <= targetWidth) {
                        resultLine = testLine;
                        resultMiddle = middle;

                        lower = middle + 1;
                    } else {
                        upper = middle - 1;
                    }
                }

                textWords = textWords.slice(resultMiddle, textWords.length);
            }

            lines.push(resultLine);
        }

        return lines;
    }

    renderLine = (line, i, arr) => {
        if (i === arr.length - 1) {
            return {line};
        } else {
            return [
                {line},

            ];
        }
    };

    render() {
        let {
            refs: {
                target
            },
            props,
            props: {
                children,
                ellipsis,
                lines
            }
        } = this;

        let text = children;

        if (target) {
            if (lines > 0) {
                text = this.getLines().map(this.renderLine);
            } else {
                this.resetTargetStyle();
            }
        }

        return (

                {text}

                    {children}
                    {ellipsis}

        );
    }

    styles = {
        raw: {
            display: 'none'
        }
    };
};
<|endoftext|>


<commit_before>// Copyright (c) 2017 The Regents of the University of Michigan.
// All Rights Reserved. Licensed according to the terms of the Revised
// BSD License. See LICENSE.txt for details.

const processTree = require("../lib/process-tree");

let treeSpy = function(logTree) {
  let spy = { };

  spy.tree = logTree;

  spy.storeProcess = function(process) {
    spy.process = process;
  };

  spy.messages = [ ];
  spy.log = function(message) {
    spy.messages.push(message)
  };

  return spy;
};

let spy;
let spyOnTree = async function(done, setup) {
  spy = await processTree(treeSpy, setup)
  done();
};

describe("a processTree with no children", () => {
  beforeEach(done => {
    spyOnTree(done, function(o) { });
  });

  it("returns a spy", () => {
    expect(spy.tree).toBeDefined();
  });

  it("logs two messages once finished running", done => {
    spy.process.then(value => {
      expect(spy.messages.length).toBe(2);

      done();
    });
  });
});

describe("a lone processTree with some setup time", () => {
  beforeEach(done => {
    spyOnTree(done, function(o) {
      o.run = () => new Promise(function(resolve, reject) {
        setTimeout(() => {resolve();}, 50);
      });
    });
  });

  it("doesn't log both messages right away", () => {
    expect(spy.messages.length).toBeLessThan(2);
  });

  it("logs both messages in the end", done => {
    spy.process.then(value => {
      expect(spy.messages.length).toBe(2);

      done();
    });
  });
});

describe("a processTree with one child and some delays", () => {
  beforeEach(done => {
    spyOnTree(done, function(o) {
      o.add(() => {});

      o.runBefore = () => new Promise(function(resolve, reject) {
        setTimeout(() => {
          o.log("info", "run before");
          resolve();
        }, 10);
      });

      o.run = () => new Promise(function(resolve, reject) {
        setTimeout(() => {
          o.log("info", "run");
          resolve();
        }, 10);
      });

      o.runAfter = () => new Promise(function(resolve, reject) {
        setTimeout(() => {
          o.log("info", "run after");
          resolve();
        }, 10);
      });
    });
  });

  describe("the resulting log", () => {
    beforeEach(done => {
      spy.process.then(value => {done();});
    });

    it("has 7 messages", () => {
      expect(spy.messages.length).toBe(7);
    });

    it("starts with a 'begin' message", () => {
      expect(spy.messages[0][1]).toBe("begin");
    });

    it("runs runBefore() first", () => {
      expect(spy.messages[1][2]).toBe("run before");
    });

    it("runs run() after runBefore()", () => {
      expect(spy.messages[2][2]).toBe("run");
    });

    it("logs a second 'begin' after run()", () => {
      expect(spy.messages[3][1]).toBe("begin");
    });

    it("logs a 'done' right after the second 'begin'", () => {
      expect(spy.messages[4][1]).toBe("done");
    });

    it("runs runAfter() after children are done", () => {
      expect(spy.messages[5][2]).toBe("run after");
    });

    it("finishes with a final 'done'", () => {
      expect(spy.messages[6][1]).toBe("done");
    });
  });
});

describe("a binary processTree with four grandchildren", () => {
  beforeEach(done => {
    let add_two_leaf_nodes = function(o) {
      o.add(() => {});
      o.add(() => {});
    };

    let add_two_parent_nodes = function(o) {
      o.add(add_two_leaf_nodes);
      o.add(add_two_leaf_nodes);
    };

    spyOnTree(done, add_two_parent_nodes);
  });

  it("can be created", () => {
  });
});
<commit_msg>:white_check_mark: Binary 4-leaf tree logs 14 messages<commit_after>// Copyright (c) 2017 The Regents of the University of Michigan.
// All Rights Reserved. Licensed according to the terms of the Revised
// BSD License. See LICENSE.txt for details.

const processTree = require("../lib/process-tree");

let treeSpy = function(logTree) {
  let spy = { };

  spy.tree = logTree;

  spy.storeProcess = function(process) {
    spy.process = process;
  };

  spy.messages = [ ];
  spy.log = function(message) {
    spy.messages.push(message)
  };

  return spy;
};

let spy;
let spyOnTree = async function(done, setup) {
  spy = await processTree(treeSpy, setup)
  done();
};

describe("a processTree with no children", () => {
  beforeEach(done => {
    spyOnTree(done, function(o) { });
  });

  it("returns a spy", () => {
    expect(spy.tree).toBeDefined();
  });

  it("logs two messages once finished running", done => {
    spy.process.then(value => {
      expect(spy.messages.length).toBe(2);

      done();
    });
  });
});

describe("a lone processTree with some setup time", () => {
  beforeEach(done => {
    spyOnTree(done, function(o) {
      o.run = () => new Promise(function(resolve, reject) {
        setTimeout(() => {resolve();}, 50);
      });
    });
  });

  it("doesn't log both messages right away", () => {
    expect(spy.messages.length).toBeLessThan(2);
  });

  it("logs both messages in the end", done => {
    spy.process.then(value => {
      expect(spy.messages.length).toBe(2);

      done();
    });
  });
});

describe("a processTree with one child and some delays", () => {
  beforeEach(done => {
    spyOnTree(done, function(o) {
      o.add(() => {});

      o.runBefore = () => new Promise(function(resolve, reject) {
        setTimeout(() => {
          o.log("info", "run before");
          resolve();
        }, 10);
      });

      o.run = () => new Promise(function(resolve, reject) {
        setTimeout(() => {
          o.log("info", "run");
          resolve();
        }, 10);
      });

      o.runAfter = () => new Promise(function(resolve, reject) {
        setTimeout(() => {
          o.log("info", "run after");
          resolve();
        }, 10);
      });
    });
  });

  describe("the resulting log", () => {
    beforeEach(done => {
      spy.process.then(value => {done();});
    });

    it("has 7 messages", () => {
      expect(spy.messages.length).toBe(7);
    });

    it("starts with a 'begin' message", () => {
      expect(spy.messages[0][1]).toBe("begin");
    });

    it("runs runBefore() first", () => {
      expect(spy.messages[1][2]).toBe("run before");
    });

    it("runs run() after runBefore()", () => {
      expect(spy.messages[2][2]).toBe("run");
    });

    it("logs a second 'begin' after run()", () => {
      expect(spy.messages[3][1]).toBe("begin");
    });

    it("logs a 'done' right after the second 'begin'", () => {
      expect(spy.messages[4][1]).toBe("done");
    });

    it("runs runAfter() after children are done", () => {
      expect(spy.messages[5][2]).toBe("run after");
    });

    it("finishes with a final 'done'", () => {
      expect(spy.messages[6][1]).toBe("done");
    });
  });
});

describe("a binary processTree with four grandchildren", () => {
  beforeEach(done => {
    let add_two_leaf_nodes = function(o) {
      o.add(() => {});
      o.add(() => {});
    };

    let add_two_parent_nodes = function(o) {
      o.add(add_two_leaf_nodes);
      o.add(add_two_leaf_nodes);
    };

    spyOnTree(done, add_two_parent_nodes);
  });

  describe("the resulting log", () => {
    beforeEach(done => {
      spy.process.then(value => {done();});
    });

    it("has 14 messages", () => {
      expect(spy.messages.length).toBe(14);
    });
  });
});
<|endoftext|>


<commit_before>import { remote, webFrame } from 'electron';
import jetpack from 'fs-jetpack';
import path from 'path';
import spellchecker from 'spellchecker';
const { app } = remote;

class SpellCheck {
	constructor() {
		this.dictionaries = [];
		this.enabledDictionaries = [];
		this.isMultiLanguage = false;
		this.dictionariesPath = null;
	}

	async load() {
		await this.loadDictionaries();
		this.setDefaultEnabledDictionaries();
	}

	async loadDictionaries() {
		const embeddedDictionaries = spellchecker.getAvailableDictionaries();

		const directory = jetpack.cwd(app.getAppPath(), app.getAppPath().endsWith('app.asar') ? '..' : '.', 'dictionaries');
		const installedDictionaries = (await directory.findAsync({ matching: '*.{aff,dic}' }))
			.map((fileName) => path.basename(fileName, path.extname(fileName)));

		this.dictionariesPath = directory.path();
		this.dictionaries = Array.from(new Set([...embeddedDictionaries, ...installedDictionaries])).sort();
		this.isMultiLanguage = embeddedDictionaries.length > 0 && process.platform !== 'win32';
	}

	setDefaultEnabledDictionaries() {
		const selectedDictionaries = (() => {
			try {
				const enabledDictionaries = JSON.parse(localStorage.getItem('spellcheckerDictionaries'));
				return Array.isArray(enabledDictionaries) ? enabledDictionaries.map(String) : null;
			} catch (error) {
				console.error(error);
				return null;
			}
		})();

		if (selectedDictionaries) {
			this.enable(...selectedDictionaries);
			return;
		}

		const userLanguage = localStorage.getItem('userLanguage');
		if (userLanguage && this.enable(this.userLanguage)) {
			return;
		}

		const navigatorLanguage = navigator.language;
		if (this.enable(navigatorLanguage)) {
			return;
		}

		if (this.enable('en_US')) {
			return;
		}
	}

	filterDictionaries(...dictionaries) {
		return dictionaries.map((dictionary) => {
			const matches = /^(\w+?)[-_](\w+)$/.exec(dictionary);

			const dictionaries = matches ?
				[`${ matches[1] }_${ matches[2] }`, `${ matches[1] }-${ matches[2] }`, matches[1]] :
				[dictionary];

			return dictionaries.find((dictionary) => this.dictionaries.includes(dictionary));
		}).filter(Boolean);
	}

	enable(...dictionaries) {
		dictionaries = this.filterDictionaries(dictionaries);

		if (this.isMultiLanguage) {
			this.enabledDictionaries = [
				...this.enabledDictionaries,
				...dictionaries,
			];
		} else {
			this.enabledDictionaries = [dictionaries[0]];
		}

		localStorage.setItem('spellcheckerDictionaries', JSON.stringify(this.enabledDictionaries));

		return this.enabledDictionaries.length > 0;
	}

	disable(...dictionaries) {
		dictionaries = this.filterDictionaries(dictionaries);

		this.enabledDictionaries = this.enabledDictionaries.filter((dictionary) => !dictionaries.includes(dictionary));
		localStorage.setItem('spellcheckerDictionaries', JSON.stringify(this.enabledDictionaries));
	}

	isCorrect(text) {
		if (!this.enabledDictionaries.length) {
			return true;
		}

		return this.enabledDictionaries.every((dictionary) => {
			spellchecker.setDictionary(dictionary, this.dictionariesPath);
			return !spellchecker.isMisspelled(text);
		});
	}

	getCorrections(text) {
		text = text.trim();

		if (text === '' || this.isCorrect(text)) {
			return null;
		}

		return Array.from(new Set(
			this.enabledDictionaries.flatMap((language) => {
				spellchecker.setDictionary(language, this.dictionariesPath);
				return spellchecker.getCorrectionsForMisspelling(text);
			})
		));
	}

	async installDictionaries(filePaths) {
		for (const filePath of filePaths) {
			const name = filePath.basename(filePath, filePath.extname(filePath));
			const basename = filePath.basename(filePath);
			const newPath = filePath.join(this.dictionariesPath, basename);

			await jetpack.copyAsync(filePath, newPath);

			if (!this.dictionaries.includes(name)) {
				this.dictionaries.push(name);
			}
		}
	}
}

export const spellchecking = new SpellCheck;

export default () => {
	spellchecking.load();

	const spellCheck = (text) => spellchecking.isCorrect(text);
	webFrame.setSpellCheckProvider('', false, { spellCheck });
};
<commit_msg>[FIX] Apply memoization to spell checking (#1132)<commit_after>import { remote, webFrame } from 'electron';
import jetpack from 'fs-jetpack';
import mem from 'mem';
import path from 'path';
import spellchecker from 'spellchecker';
const { app } = remote;

class SpellCheck {
	constructor() {
		this.dictionaries = [];
		this.enabledDictionaries = [];
		this.isMultiLanguage = false;
		this.dictionariesPath = null;
	}

	async load() {
		await this.loadDictionaries();
		this.setDefaultEnabledDictionaries();
	}

	async loadDictionaries() {
		const embeddedDictionaries = spellchecker.getAvailableDictionaries();

		const directory = jetpack.cwd(app.getAppPath(), app.getAppPath().endsWith('app.asar') ? '..' : '.', 'dictionaries');
		const installedDictionaries = (await directory.findAsync({ matching: '*.{aff,dic}' }))
			.map((fileName) => path.basename(fileName, path.extname(fileName)));

		this.dictionariesPath = directory.path();
		this.dictionaries = Array.from(new Set([...embeddedDictionaries, ...installedDictionaries])).sort();
		this.isMultiLanguage = embeddedDictionaries.length > 0 && process.platform !== 'win32';
	}

	setDefaultEnabledDictionaries() {
		const selectedDictionaries = (() => {
			try {
				const enabledDictionaries = JSON.parse(localStorage.getItem('spellcheckerDictionaries'));
				return Array.isArray(enabledDictionaries) ? enabledDictionaries.map(String) : null;
			} catch (error) {
				console.error(error);
				return null;
			}
		})();

		if (selectedDictionaries) {
			this.enable(...selectedDictionaries);
			return;
		}

		const userLanguage = localStorage.getItem('userLanguage');
		if (userLanguage && this.enable(this.userLanguage)) {
			return;
		}

		const navigatorLanguage = navigator.language;
		if (this.enable(navigatorLanguage)) {
			return;
		}

		if (this.enable('en_US')) {
			return;
		}
	}

	filterDictionaries(...dictionaries) {
		return dictionaries.map((dictionary) => {
			const matches = /^(\w+?)[-_](\w+)$/.exec(dictionary);

			const dictionaries = matches ?
				[`${ matches[1] }_${ matches[2] }`, `${ matches[1] }-${ matches[2] }`, matches[1]] :
				[dictionary];

			return dictionaries.find((dictionary) => this.dictionaries.includes(dictionary));
		}).filter(Boolean);
	}

	enable(...dictionaries) {
		dictionaries = this.filterDictionaries(dictionaries);

		if (this.isMultiLanguage) {
			this.enabledDictionaries = [
				...this.enabledDictionaries,
				...dictionaries,
			];
		} else {
			this.enabledDictionaries = [dictionaries[0]];
		}

		localStorage.setItem('spellcheckerDictionaries', JSON.stringify(this.enabledDictionaries));

		this.updateChecker();

		return this.enabledDictionaries.length > 0;
	}

	disable(...dictionaries) {
		dictionaries = this.filterDictionaries(dictionaries);

		this.enabledDictionaries = this.enabledDictionaries.filter((dictionary) => !dictionaries.includes(dictionary));
		localStorage.setItem('spellcheckerDictionaries', JSON.stringify(this.enabledDictionaries));

		this.updateChecker();
	}

	updateChecker() {
		try {
			if (this.enabledDictionaries.length === 0) {
				this.checker = () => true;
				return;
			}

			if (this.enabledDictionaries.length === 1) {
				let enabled = false;
				this.checker = mem((text) => {
					if (!enabled) {
						spellchecker.setDictionary(this.enabledDictionaries[0], this.dictionariesPath);
						enabled = true;
					}
					return !spellchecker.isMisspelled(text);
				});
				return;
			}

			const singleDictionaryChecker = mem(
				((dictionariesPath, dictionary, text) => {
					spellchecker.setDictionary(dictionary, dictionariesPath);
					return !spellchecker.isMisspelled(text);
				})
					.bind(null, this.dictionariesPath)
			);

			this.checker = mem(
				((dictionaries, text) => dictionaries.every((dictionary) => singleDictionaryChecker(dictionary, text)))
					.bind(null, this.enabledDictionaries)
			);
		} finally {
			webFrame.setSpellCheckProvider('', false, { spellCheck: this.checker });
		}
	}

	isCorrect(text) {
		return this.checker(text);
	}

	getCorrections(text) {
		text = text.trim();

		if (text === '' || this.isCorrect(text)) {
			return null;
		}

		return Array.from(new Set(
			this.enabledDictionaries.flatMap((language) => {
				spellchecker.setDictionary(language, this.dictionariesPath);
				return spellchecker.getCorrectionsForMisspelling(text);
			})
		));
	}

	async installDictionaries(filePaths) {
		for (const filePath of filePaths) {
			const name = filePath.basename(filePath, filePath.extname(filePath));
			const basename = filePath.basename(filePath);
			const newPath = filePath.join(this.dictionariesPath, basename);

			await jetpack.copyAsync(filePath, newPath);

			if (!this.dictionaries.includes(name)) {
				this.dictionaries.push(name);
			}
		}
	}
}

export const spellchecking = new SpellCheck;

export default () => {
	spellchecking.load();
};
<|endoftext|>


<commit_before>import chai     from "chai";
import url_reducer        from "../../src/reducers/url";
import Actions        from "../../src/Actions";
import T        from "../../src/constants/ActionTypes";

chai.should();

describe("url reducer", () => {

  it("should be a function", () => {
    url_reducer.should.be.a("function");
  });

  describe("urlSetSearch", () => {
    it("should put search text into the url", () => {
      let action = null;
      const first_query_part = "#/?center=47.0000,8.0000&zoom=13&";

      const dispatch = (arg) => {
        action = arg;
      };
      const getState = () => ({
        map: {
          center: {lat: 47.0, lng: 8.0},
          zoom: 13
        }
      });

      Actions.urlSetSearch("blabla")(dispatch, getState);
      url_reducer({}, action).should.equal(first_query_part + "search=blabla");

      Actions.urlSetSearch(" blabla  #foo blubb  #bar   blabla")(dispatch, getState);
      url_reducer({}, action).should.equal(first_query_part + "search=blabla%20%23foo%20blubb%20%23bar%20blabla");

      Actions.urlSetSearch("blabla #")(dispatch, getState);
      url_reducer({}, action).should.equal(first_query_part + "search=blabla%20%23");

      Actions.urlSetSearch("blabla # ")(dispatch, getState);
      url_reducer({}, action).should.equal(first_query_part + "search=blabla%20%23%20");

      return true;
    });
  });

  describe("urlSetCurrentEntry", () => {
    it("should put entry into url", () => {
      let action = null;
      const dispatch = (arg) => {
        action = arg;
      };
      const getState = () => ({
        map: {
          center: {lat: 47.0, lng: 8.0},
          zoom: 13
        },
        search: {
          text: "blabla"
        }
      });

      Actions.urlSetCurrentEntry("f816a9259f9f4fe497b17f7d7b8611b5")(dispatch, getState);
      window.location.hash = "#/?entry=0b6bf94f5be84bd1a6faccabb1b93475&zoom=13";
      url_reducer({}, action).should.equal("#/?entry=f816a9259f9f4fe497b17f7d7b8611b5&zoom=13");
    });
  });

  describe("urlSetCenter", () => {
    it("should put center into url", () => {
      let action = null;
      const dispatch = (arg) => {
        action = arg;
      };
      const getState = () => ({
        map: {
          center: {lat: 47.0, lng: 8.0},
          zoom: 13
        },
        search: {
          text: "blabla"
        }
      });

      Actions.urlSetCenter({lat: 1.0, lng: 2.0})(dispatch, getState);
      window.location.hash = "#/?center=47.0,9.0&zoom=13&search=blabla";
      url_reducer({hash: "#/?center=47.0,9.0&zoom=13&search=blabla"}, action)
        .should.equal("#/?center=1.0000,2.0000&zoom=13&search=blabla");
    });

    it("should not change URL when entry is shown", () => {
      let action = null;
      const dispatch = (arg) => {
        action = arg;
      };
      const getState = () => ({
        map: {
          center: {lat: 47.0, lng: 8.0},
          zoom: 13
        },
        search: {
          text: "blabla"
        }
      });

      Actions.urlSetCenter({lat: 1.0, lng: 2.0})(dispatch, getState);
      window.location.hash = "#/?entry=f816a9259f9f4fe497b17f7d7b8611b5";
      url_reducer({hash: "#/?entry=f816a9259f9f4fe497b17f7d7b8611b5"}, action)
        .should.equal("#/?entry=f816a9259f9f4fe497b17f7d7b8611b5&zoom=13");
    });
  });

  describe("urlSetZoom", () => {
    it("should put zoom into url", () => {
      let action = null;
      const dispatch = (arg) => {
        action = arg;
      };
      const getState = () => ({
        map: {
          center: {lat: 47.0, lng: 8.0},
          zoom: 13
        },
        search: {
          text: "blabla"
        }
      });

      Actions.urlSetZoom(15)(dispatch, getState);
      window.location.hash = "#/?center=47.0,9.0&zoom=13&search=blabla";
      url_reducer({}, action).should.equal("#/?center=47.0000,8.0000&zoom=15&search=blabla");
    });
  });
});<commit_msg>fix(routing-tests): make tests pass again)<commit_after>import chai     from "chai";
import url_reducer        from "../../src/reducers/url";
import Actions        from "../../src/Actions";
import T        from "../../src/constants/ActionTypes";

chai.should();

describe("url reducer", () => {

  it("should be a function", () => {
    url_reducer.should.be.a("function");
  });

  describe("urlSetSearch", () => {
    it("should put search text into the url", () => {
      let action = null;
      const first_query_part = "#/?center=47.0000,8.0000&zoom=13&";

      const dispatch = (arg) => {
        action = arg;
      };
      const getState = () => ({
        map: {
          center: {lat: 47.0, lng: 8.0},
          zoom: 13
        }
      });

      Actions.urlSetSearch("blabla")(dispatch, getState);
      url_reducer({}, action).url.should.equal(first_query_part + "search=blabla");

      Actions.urlSetSearch(" blabla  #foo blubb  #bar   blabla")(dispatch, getState);
      url_reducer({}, action).url.should.equal(first_query_part + "search=blabla%20%23foo%20blubb%20%23bar%20blabla");

      Actions.urlSetSearch("blabla #")(dispatch, getState);
      url_reducer({}, action).url.should.equal(first_query_part + "search=blabla%20%23");

      Actions.urlSetSearch("blabla # ")(dispatch, getState);
      url_reducer({}, action).url.should.equal(first_query_part + "search=blabla%20%23%20");

      return true;
    });
  });

  describe("urlSetCurrentEntry", () => {
    it("should put entry into url", () => {
      let action = null;
      const dispatch = (arg) => {
        action = arg;
      };
      const getState = () => ({
        map: {
          center: {lat: 47.0, lng: 8.0},
          zoom: 13
        },
        search: {
          text: "blabla"
        }
      });

      Actions.urlSetCurrentEntry("f816a9259f9f4fe497b17f7d7b8611b5")(dispatch, getState);
      window.location.hash = "#/?entry=0b6bf94f5be84bd1a6faccabb1b93475&zoom=13";
      url_reducer({}, action).url.should.equal("#/?entry=f816a9259f9f4fe497b17f7d7b8611b5&zoom=13");
    });
  });

  describe("urlSetCenter", () => {
    it("should put center into url", () => {
      let action = null;
      const dispatch = (arg) => {
        action = arg;
      };
      const getState = () => ({
        map: {
          center: {lat: 47.0, lng: 8.0},
          zoom: 13
        },
        search: {
          text: "blabla"
        }
      });

      Actions.urlSetCenter({lat: 1.0, lng: 2.0})(dispatch, getState);
      window.location.hash = "#/?center=47.0,9.0&zoom=13&search=blabla";
      url_reducer({hash: "#/?center=47.0,9.0&zoom=13&search=blabla"}, action)
        .url.should.equal("#/?center=1.0000,2.0000&zoom=13&search=blabla");
    });

    it("should not change URL when entry is shown", () => {
      let action = null;
      const dispatch = (arg) => {
        action = arg;
      };
      const getState = () => ({
        map: {
          center: {lat: 47.0, lng: 8.0},
          zoom: 13
        },
        search: {
          text: "blabla"
        }
      });

      Actions.urlSetCenter({lat: 1.0, lng: 2.0})(dispatch, getState);
      window.location.hash = "#/?entry=f816a9259f9f4fe497b17f7d7b8611b5";
      url_reducer({hash: "#/?entry=f816a9259f9f4fe497b17f7d7b8611b5"}, action)
        .url.should.equal("#/?entry=f816a9259f9f4fe497b17f7d7b8611b5&zoom=13");
    });
  });

  describe("urlSetZoom", () => {
    it("should put zoom into url", () => {
      let action = null;
      const dispatch = (arg) => {
        action = arg;
      };
      const getState = () => ({
        map: {
          center: {lat: 47.0, lng: 8.0},
          zoom: 13
        },
        search: {
          text: "blabla"
        }
      });

      Actions.urlSetZoom(15)(dispatch, getState);
      window.location.hash = "#/?center=47.0,9.0&zoom=13&search=blabla";
      url_reducer({}, action).url.should.equal("#/?center=47.0000,8.0000&zoom=15&search=blabla");
    });
  });
});<|endoftext|>


<commit_before>import crypto from 'crypto';
import logger from 'winston';
import {IPreset} from '../interface';
import {Utils, AdvancedBuffer} from '../../utils';

const PADDING_LEN = 12;
const HASH_SALT = 'blinksocks';

// available ciphers
const ciphers = [
  'aes-128-ctr', 'aes-192-ctr', 'aes-256-ctr',
  'aes-128-cfb', 'aes-192-cfb', 'aes-256-cfb',
  'aes-128-ofb', 'aes-192-ofb', 'aes-256-ofb',
  'aes-128-cbc', 'aes-192-cbc', 'aes-256-cbc'
];

// available hash functions
const hashes = ['sha1', 'sha256', 'sha512'];

// map hashes to hmac lengths
const hmacLens = {'sha1': 20, 'sha256': 32, 'sha512': 64};

/**
 * generate strong and valid key
 * @param cipher
 * @param key
 * @returns {Buffer}
 */
function getStrongKey(cipher, key) {
  const hash = crypto.createHash('sha256');
  const keyLen = cipher.split('-')[1] / 8;
  hash.update(Buffer.concat([Buffer.from(key), Buffer.from(HASH_SALT)]));
  return hash.digest().slice(0, keyLen);
}

/**
 * @description
 *   This protocol implementation is Authenticated Encryption(AE) which simultaneously
 *   provides confidentiality, integrity, and authenticity assurances on the data.
 *
 * @params
 *   cipher (String): How to encrypt/decrypt the header bytes.
 *   hash (String): A hash method for calculating HMAC.
 *
 * @examples
 *   "protocol": "aead"
 *   "protocol_params": "aes-128-cbc,sha256"
 *
 * @protocol
 *
 *   # TCP handshake & chunk
 *   +---------------+-----+----------+-------------------+----------+
 *   |    PADDING    | LEN |  HMAC-A  |      PAYLOAD      |  HMAC-B  |
 *   +---------------+-----+----------+-------------------+----------+
 *   |      12       |  4  |  Fixed   |      Variable     |  Fixed   |
 *   +---------------+-----+----------+-------------------+----------+
 *   |<----- header ------>|
 *
 * @explain
 *   1. LEN is the total length of the packet.
 *   2. PADDING is random generated.
 *   3. HMAC-A = mac(encrypt(header)).
 *   4. HMAC-B = mac(encrypt(PAYLOAD)).
 *   5. The length of HMAC depends on message digest algorithm.
 *   6. Encrypt-then-MAC (EtM) is performed for calculating HMAC.
 *
 * @reference
 *   [1] Protocol inspired by shadowsocks
 *       https://shadowsocks.org/en/spec/AEAD.html
 *   [2] Encrypt-then-MAC (EtM)
 *       https://en.wikipedia.org/wiki/Authenticated_encryption#Encrypt-then-MAC_.28EtM.29
 */
export default class AeadProtocol extends IPreset {

  _hmacLen = 0;

  _cipher = '';

  _hash = '';

  _key = null;

  _adBuf = null;

  _fNext = null;

  _bNext = null;

  constructor(cipher, hash) {
    super();
    if (typeof cipher === 'undefined' || cipher === '') {
      throw Error('\'protocol_params\' requires [cipher] parameter.');
    }
    if (!ciphers.includes(cipher)) {
      throw Error(`cipher \'${cipher}\' is not supported.`);
    }
    if (typeof hash === 'undefined' || hash === '') {
      throw Error('\'protocol_params\' requires [hash] parameter.');
    }
    if (!hashes.includes(hash)) {
      throw Error(`hash \'${hash}\' is not supported.`);
    }
    this._cipher = cipher;
    this._hash = hash;
    this._key = getStrongKey(cipher, __KEY__);
    this._adBuf = new AdvancedBuffer({getPacketLength: this.onGetLength.bind(this)});
    this._adBuf.on('data', (data) => this.onReceived(data));
    this._hmacLen = hmacLens[this._hash];
  }

  clientOut(args) {
    return this._out(args);
  }

  serverIn({buffer, next}) {
    this._fNext = next;
    this._adBuf.put(buffer);
  }

  serverOut(args) {
    return this._out(args);
  }

  clientIn({buffer, next}) {
    this._bNext = next;
    this._adBuf.put(buffer);
  }

  _out({buffer}) {
    const encBuffer = this.encrypt(buffer);
    const encHeader = this.encrypt(Buffer.concat([
      crypto.randomBytes(PADDING_LEN),
      Utils.numberToUIntBE(16 + this._hmacLen + encBuffer.length + this._hmacLen, 4)
    ]), false);
    const hmacA = this.createHmac(encHeader);
    const hmacB = this.createHmac(encBuffer);
    const out = Buffer.concat([encHeader, hmacA, encBuffer, hmacB]);
    if (__IS_CLIENT__) {
      logger.info(`ClientOut(${out.length} bytes): ${out.slice(0, 60).toString('hex')}`);
    } else {
      logger.info(`ServerOut(${out.length} bytes): ${out.slice(0, 60).toString('hex')}`);
    }
    return out;
  }

  /**
   * @param buffer
   * @returns {Number}
   */
  onGetLength(buffer) {
    if (buffer.length < (16 + 2 * this._hmacLen)) {
      logger.warn(`dropped unexpected packet (${buffer.length} bytes) received from client: ${buffer.slice(0, 60).toString('hex')}`);
      return -1;
    }
    const encLen = buffer.slice(0, 16);
    const expHmacA = this.createHmac(encLen);
    const hmacA = buffer.slice(16, 16 + this._hmacLen);
    if (!hmacA.equals(expHmacA)) {
      logger.error(`dropped unexpected packet (${buffer.length} bytes) received from server: wrong HMAC-A ${buffer.slice(0, 60).toString('hex')}`);
      return -1;
    }
    // safely decrypt then get length
    return this.decrypt(encLen, false).readUIntBE(PADDING_LEN, 4);
  }

  /**
   * triggered when all chunks received
   * @param packet
   */
  onReceived(packet) {
    if (__IS_CLIENT__) {
      logger.info(`ClientIn(${packet.length} bytes): ${packet.slice(0, 60).toString('hex')}`);
    } else {
      logger.info(`ServerIn(${packet.length} bytes): ${packet.slice(0, 60).toString('hex')}`);
    }
    const _packet = packet.slice(16 + this._hmacLen);
    const hmacB = _packet.slice(-this._hmacLen);
    const expHmacB = this.createHmac(_packet.slice(0, -this._hmacLen));
    if (!hmacB.equals(expHmacB)) {
      logger.error(`dropped unexpected packet (${packet.length} bytes) received from ${__IS_CLIENT__ ? 'server' : 'client'}: wrong HMAC-B ${packet.slice(0, 60).toString('hex')}`);
      // TODO: maybe notify to disconnect rather than timeout?
      return;
    }
    const next = __IS_CLIENT__ ? this._bNext : this._fNext;
    next(this.decrypt(_packet.slice(0, -this._hmacLen)));
  }

  encrypt(buffer, padding = true) {
    const cipher = crypto.createCipher(this._cipher, this._key);
    cipher.setAutoPadding(padding);
    return Buffer.concat([cipher.update(buffer), cipher.final()]);
  }

  decrypt(buffer, padding = true) {
    const decipher = crypto.createDecipher(this._cipher, this._key);
    decipher.setAutoPadding(padding);
    return Buffer.concat([decipher.update(buffer), decipher.final()]);
  }

  createHmac(buffer) {
    const hmac = crypto.createHmac(this._hash, this._key);
    hmac.update(buffer);
    return hmac.digest();
  }

}
<commit_msg>fix(presets/protocol): should not drop buffer if it too short to calc HMAC, should continue to receive<commit_after>import crypto from 'crypto';
import logger from 'winston';
import {IPreset} from '../interface';
import {Utils, AdvancedBuffer} from '../../utils';

const PADDING_LEN = 12;
const HASH_SALT = 'blinksocks';

// available ciphers
const ciphers = [
  'aes-128-ctr', 'aes-192-ctr', 'aes-256-ctr',
  'aes-128-cfb', 'aes-192-cfb', 'aes-256-cfb',
  'aes-128-ofb', 'aes-192-ofb', 'aes-256-ofb',
  'aes-128-cbc', 'aes-192-cbc', 'aes-256-cbc'
];

// available hash functions
const hashes = ['sha1', 'sha256', 'sha512'];

// map hashes to hmac lengths
const hmacLens = {'sha1': 20, 'sha256': 32, 'sha512': 64};

/**
 * generate strong and valid key
 * @param cipher
 * @param key
 * @returns {Buffer}
 */
function getStrongKey(cipher, key) {
  const hash = crypto.createHash('sha256');
  const keyLen = cipher.split('-')[1] / 8;
  hash.update(Buffer.concat([Buffer.from(key), Buffer.from(HASH_SALT)]));
  return hash.digest().slice(0, keyLen);
}

/**
 * @description
 *   This protocol implementation is Authenticated Encryption(AE) which simultaneously
 *   provides confidentiality, integrity, and authenticity assurances on the data.
 *
 * @params
 *   cipher (String): How to encrypt/decrypt the header bytes.
 *   hash (String): A hash method for calculating HMAC.
 *
 * @examples
 *   "protocol": "aead"
 *   "protocol_params": "aes-128-cbc,sha256"
 *
 * @protocol
 *
 *   # TCP handshake & chunk
 *   +---------------+-----+----------+-------------------+----------+
 *   |    PADDING    | LEN |  HMAC-A  |      PAYLOAD      |  HMAC-B  |
 *   +---------------+-----+----------+-------------------+----------+
 *   |      12       |  4  |  Fixed   |      Variable     |  Fixed   |
 *   +---------------+-----+----------+-------------------+----------+
 *   |<----- header ------>|
 *
 * @explain
 *   1. LEN is the total length of the packet.
 *   2. PADDING is random generated.
 *   3. HMAC-A = mac(encrypt(header)).
 *   4. HMAC-B = mac(encrypt(PAYLOAD)).
 *   5. The length of HMAC depends on message digest algorithm.
 *   6. Encrypt-then-MAC (EtM) is performed for calculating HMAC.
 *
 * @reference
 *   [1] Protocol inspired by shadowsocks
 *       https://shadowsocks.org/en/spec/AEAD.html
 *   [2] Encrypt-then-MAC (EtM)
 *       https://en.wikipedia.org/wiki/Authenticated_encryption#Encrypt-then-MAC_.28EtM.29
 */
export default class AeadProtocol extends IPreset {

  _hmacLen = 0;

  _cipher = '';

  _hash = '';

  _key = null;

  _adBuf = null;

  _fNext = null;

  _bNext = null;

  constructor(cipher, hash) {
    super();
    if (typeof cipher === 'undefined' || cipher === '') {
      throw Error('\'protocol_params\' requires [cipher] parameter.');
    }
    if (!ciphers.includes(cipher)) {
      throw Error(`cipher \'${cipher}\' is not supported.`);
    }
    if (typeof hash === 'undefined' || hash === '') {
      throw Error('\'protocol_params\' requires [hash] parameter.');
    }
    if (!hashes.includes(hash)) {
      throw Error(`hash \'${hash}\' is not supported.`);
    }
    this._cipher = cipher;
    this._hash = hash;
    this._key = getStrongKey(cipher, __KEY__);
    this._adBuf = new AdvancedBuffer({getPacketLength: this.onGetLength.bind(this)});
    this._adBuf.on('data', (data) => this.onReceived(data));
    this._hmacLen = hmacLens[this._hash];
  }

  clientOut(args) {
    return this._out(args);
  }

  serverIn({buffer, next}) {
    this._fNext = next;
    this._adBuf.put(buffer);
  }

  serverOut(args) {
    return this._out(args);
  }

  clientIn({buffer, next}) {
    this._bNext = next;
    this._adBuf.put(buffer);
  }

  _out({buffer}) {
    const encBuffer = this.encrypt(buffer);
    const encHeader = this.encrypt(Buffer.concat([
      crypto.randomBytes(PADDING_LEN),
      Utils.numberToUIntBE(16 + this._hmacLen + encBuffer.length + this._hmacLen, 4)
    ]), false);
    const hmacA = this.createHmac(encHeader);
    const hmacB = this.createHmac(encBuffer);
    const out = Buffer.concat([encHeader, hmacA, encBuffer, hmacB]);
    if (__IS_CLIENT__) {
      logger.info(`ClientOut(${out.length} bytes): ${out.slice(0, 60).toString('hex')}`);
    } else {
      logger.info(`ServerOut(${out.length} bytes): ${out.slice(0, 60).toString('hex')}`);
    }
    return out;
  }

  /**
   * @param buffer
   * @returns {Number}
   */
  onGetLength(buffer) {
    if (buffer.length < (16 + 2 * this._hmacLen)) {
      return 0; // too short, should continue to receive
    }
    const encLen = buffer.slice(0, 16);
    const expHmacA = this.createHmac(encLen);
    const hmacA = buffer.slice(16, 16 + this._hmacLen);
    if (!hmacA.equals(expHmacA)) {
      logger.error(`dropped unexpected packet (${buffer.length} bytes) received from server: wrong HMAC-A ${buffer.slice(0, 60).toString('hex')}`);
      return -1;
    }
    // safely decrypt then get length
    return this.decrypt(encLen, false).readUIntBE(PADDING_LEN, 4);
  }

  /**
   * triggered when all chunks received
   * @param packet
   */
  onReceived(packet) {
    if (__IS_CLIENT__) {
      logger.info(`ClientIn(${packet.length} bytes): ${packet.slice(0, 60).toString('hex')}`);
    } else {
      logger.info(`ServerIn(${packet.length} bytes): ${packet.slice(0, 60).toString('hex')}`);
    }
    const _packet = packet.slice(16 + this._hmacLen);
    const hmacB = _packet.slice(-this._hmacLen);
    const expHmacB = this.createHmac(_packet.slice(0, -this._hmacLen));
    if (!hmacB.equals(expHmacB)) {
      logger.error(`dropped unexpected packet (${packet.length} bytes) received from ${__IS_CLIENT__ ? 'server' : 'client'}: wrong HMAC-B ${packet.slice(0, 60).toString('hex')}`);
      // TODO: maybe notify to disconnect rather than timeout?
      return;
    }
    const next = __IS_CLIENT__ ? this._bNext : this._fNext;
    next(this.decrypt(_packet.slice(0, -this._hmacLen)));
  }

  encrypt(buffer, padding = true) {
    const cipher = crypto.createCipher(this._cipher, this._key);
    cipher.setAutoPadding(padding);
    return Buffer.concat([cipher.update(buffer), cipher.final()]);
  }

  decrypt(buffer, padding = true) {
    const decipher = crypto.createDecipher(this._cipher, this._key);
    decipher.setAutoPadding(padding);
    return Buffer.concat([decipher.update(buffer), decipher.final()]);
  }

  createHmac(buffer) {
    const hmac = crypto.createHmac(this._hash, this._key);
    hmac.update(buffer);
    return hmac.digest();
  }

}
<|endoftext|>


<commit_before>'use strict';

var logger = require('log4js').getLogger('blueprints_spec');
var components = require('../../src/components/index');

describe('blueprints page', function(){
    var testConf = components.config.tests.sanity.blueprints_spec;
    var BlueprintPage = components.ui.blueprints.BlueprintPage;

    beforeEach(function(){ logger.info('running from ' + __filename); });

    beforeEach(function(){
        components.ui.LoginPage.goTo().login('admin', 'admin');
    });

    describe('blueprint view', function() {

        beforeEach(function () {
            components.ui.blueprints.IndexPage.goToBlueprint({'name': testConf.blueprints.blueprintToRead});
        });

        //it('should have all sections', function (done) {
        //    components.ui.blueprints.BlueprintPage.goToNodes();
        //    components.ui.blueprints.BlueprintPage.goToSource();
        //    browser.sleep(1000).then(done);
        //});
    });

    describe('source view', function(){

        //it('should have working source section', function(done) {
        //    components.ui.blueprints.IndexPage.goToBlueprint({'name': testConf.blueprints.blueprintToRead});
        //    BlueprintPage.goToSource();
        //    BlueprintPage.Source.getTree();
        //    // we should be viewing the main blueprint file:
        //    BlueprintPage.Source.getFileTitle('simple-blueprint.yaml');
        //    BlueprintPage.Source.selectFile('README.md');
        //    BlueprintPage.Source.getFileTitle('README.md');
        //    BlueprintPage.Source.getFileContent();
        //
        //    browser.sleep(1000).then( done );
        //});
        //
        //it('should have proper loading message', function(done) {
        //
        //    browser.get('/#/blueprint/nodecellar1/source');
        //    browser.ignoreSynchronization = true;
        //    BlueprintPage.Source.getLoadingMessage().then(function(result) {
        //        expect(result).toBe('Generating Blueprint Source View...');
        //    });
        //
        //    browser.sleep(0).then(function(){
        //        browser.ignoreSynchronization = false;
        //        done();
        //    });
        //});
    });

    describe('blueprints page operations', function(){
        it('should create a deployment, CREATE_DEPLOYMENT_EXISTS', function (done) {
            logger.trace('start create deployment test');

            components.ui.blueprints.IndexPage.createDeployment({'name' : testConf.blueprints.blueprintToDeploy});

            var newDeploymentName = 'new-deployment-' + new Date().getTime();

            components.ui.common.Dialog.clickClose(); // lets test cancel on the way!

            function createDeployment( waitFor ) {

                components.ui.blueprints.IndexPage.createDeployment({'name': testConf.blueprints.blueprintToDeploy});
                components.ui.blueprints.CreateDeployment.setDetails({
                    name: newDeploymentName,
                    raw: components.config.tests.sanity.blueprints_spec.deployment
                });
                components.ui.blueprints.CreateDeployment.deploy();
                if ( waitFor ) {
                    components.ui.deployments.DeploymentPage.waitForInitializingToStop();
                }
            }

            createDeployment(true);

            browser.sleep(3000);
            expect(browser.getCurrentUrl()).toContain('/deployment/', 'DEPLOY_REDIRECT should redirect to deployments');

            components.ui.layout.goToBlueprints();

            createDeployment(false);

            expect(components.ui.common.Dialog.getErrorMessage()).toContain('already', 'CFY-2902 duplicate name should cause error');
            components.ui.common.Dialog.close();

            components.ui.layout.goToDeployments();
            components.ui.deployments.IndexPage.deleteDeployment({id:newDeploymentName});

            browser.sleep(1000).then(done);
        });

        it('should show deploy dialog after default button pressed', function (done) {

            logger.trace('start create deployment with default button test');

            components.ui.blueprints.IndexPage.createDeploymentWithDefaultBtn({'name' : testConf.blueprints.blueprintToDeploy});
            expect(element(by.css('#deployDialogContainer')).isPresent()).toBe(true);

            browser.sleep(500).then(function(){ done(); });
        });

        it('should upload and delete a blueprint, ENTER_BLUEPRINT_NAME', function(done){
            // todo
            //done();
            var blueprintName = 'uploaded-' + new Date().getTime();
            var uploadedBlueprint = { blueprint_id: blueprintName , 'blueprint_filename' : 'simple-blueprint.yaml', 'blueprint_location':'https://github.com/cloudify-cosmo/cloudify-nodecellar-example/archive/master.zip' };
            components.ui.blueprints.IndexPage.uploadBlueprint();

            components.ui.common.Dialog.clickClose();

            expect(components.ui.blueprints.UploadBlueprintDialog.getUploadForm().isPresent()).toBe(false,'CLOSE_BLUEPRINT_DIALOG dialog should be closed after');
            components.ui.blueprints.IndexPage.uploadBlueprint(); // reopen

            components.ui.blueprints.UploadBlueprintDialog.getBlueprintIdLabel().then(function(label){
                expect(label.toLowerCase()).toContain('blueprint id', 'CFY-2301 label should be blueprint id ...  also verifies dialog is open');
            });
            components.ui.blueprints.UploadBlueprintDialog.setDetails(uploadedBlueprint);
            components.ui.blueprints.UploadBlueprintDialog.submit();

            expect(browser.getCurrentUrl()).toContain(uploadedBlueprint.blueprint_id,'UPLOAD_REDIRECT upload should redirect to blueprint when finished');
            //components.ui.blueprints.IndexPage.waitForUploadDone({name: uploadedBlueprint.blueprint_id});
            components.ui.layout.goToBlueprints();

            components.ui.blueprints.IndexPage.deleteBlueprint({name:blueprintName});
            expect(components.ui.blueprints.IndexPage.getBlueprint({name: blueprintName }, true)).toBeUndefined('CFY-3524 delete blueprint should refresh blueprints');

            browser.sleep(1000).then(function(){ done(); });
        });

    });

    describe('topology view', function(){
        it('should open node details panel when clicking on node in topology section', function(done) {
            logger.trace('start blueprint topology section test');
            components.ui.blueprints.IndexPage.goToBlueprint({'name' : testConf.blueprints.blueprintToRead});
            components.ui.blueprints.BlueprintPage.goToTopology();
            components.ui.blueprints.BlueprintPage.Topology.clickNode(testConf.blueprints.nodeToClick);
            components.ui.blueprints.BlueprintPage.PropertiesPanel.waitForVisibility();
            components.ui.blueprints.BlueprintPage.PropertiesPanel.goToProperties();
            components.ui.blueprints.BlueprintPage.PropertiesPanel.goToRelationships();
            components.ui.blueprints.BlueprintPage.PropertiesPanel.close();

            browser.sleep(1000).then(function(){ done(); });
        });
    });

    //it('should open node details panel when clicking on node in network section', function (done) {
    //    logger.trace('start blueprint network section test');
    //    components.ui.blueprints.IndexPage.goToBlueprint({'name' : DEPLOY_BLUEPRINT_NAME});
    //    components.ui.blueprints.BlueprintPage.goToSection('Network');
    //    components.ui.blueprints.BlueprintPage.Network.getSubnets();
    //    components.ui.blueprints.BlueprintPage.Network.getRouters();
    //
    //    browser.sleep(1000).then(function(){ done(); });
    //});
    //
    //it('should open node details panel when clicking on node in nodes section', function (done) {
    //    logger.trace('start blueprint node section test');
    //    components.ui.blueprints.IndexPage.goToBlueprint({'name' : DEPLOY_BLUEPRINT_NAME});
    //    components.ui.blueprints.BlueprintPage.goToSection('Nodes');
    //    components.ui.blueprints.BlueprintPage.NodesTable.clickNode('mongod');
    //
    //    browser.sleep(1000).then(function(){ done(); });
    //});

});
<commit_msg>fix(events) fixing logs filter tests at events page CFY-4899<commit_after>'use strict';

var logger = require('log4js').getLogger('blueprints_spec');
var components = require('../../src/components/index');

describe('blueprints page', function(){
    var testConf = components.config.tests.sanity.blueprints_spec;
    var BlueprintPage = components.ui.blueprints.BlueprintPage;

    beforeEach(function(){ logger.info('running from ' + __filename); });

    beforeEach(function(){
        components.ui.LoginPage.goTo().login('admin', 'admin');
    });

    describe('blueprint view', function() {

        beforeEach(function () {
            components.ui.blueprints.IndexPage.goToBlueprint({'name': testConf.blueprints.blueprintToRead});
        });

        it('should have all sections', function (done) {
            components.ui.blueprints.BlueprintPage.goToNodes();
            components.ui.blueprints.BlueprintPage.goToSource();
            browser.sleep(1000).then(done);
        });
    });

    describe('source view', function(){

        it('should have working source section', function(done) {
            components.ui.blueprints.IndexPage.goToBlueprint({'name': testConf.blueprints.blueprintToRead});
            BlueprintPage.goToSource();
            BlueprintPage.Source.getTree();
            // we should be viewing the main blueprint file:
            BlueprintPage.Source.getFileTitle('simple-blueprint.yaml');
            BlueprintPage.Source.selectFile('README.md');
            BlueprintPage.Source.getFileTitle('README.md');
            BlueprintPage.Source.getFileContent();

            browser.sleep(1000).then( done );
        });

        it('should have proper loading message', function(done) {

            browser.get('/#/blueprint/nodecellar1/source');
            browser.ignoreSynchronization = true;
            BlueprintPage.Source.getLoadingMessage().then(function(result) {
                expect(result).toBe('Generating Blueprint Source View...');
            });

            browser.sleep(0).then(function(){
                browser.ignoreSynchronization = false;
                done();
            });
        });
    });

    describe('blueprints page operations', function(){
        it('should create a deployment, CREATE_DEPLOYMENT_EXISTS', function (done) {
            logger.trace('start create deployment test');

            components.ui.blueprints.IndexPage.createDeployment({'name' : testConf.blueprints.blueprintToDeploy});

            var newDeploymentName = 'new-deployment-' + new Date().getTime();

            components.ui.common.Dialog.clickClose(); // lets test cancel on the way!

            function createDeployment( waitFor ) {

                components.ui.blueprints.IndexPage.createDeployment({'name': testConf.blueprints.blueprintToDeploy});
                components.ui.blueprints.CreateDeployment.setDetails({
                    name: newDeploymentName,
                    raw: components.config.tests.sanity.blueprints_spec.deployment
                });
                components.ui.blueprints.CreateDeployment.deploy();
                if ( waitFor ) {
                    components.ui.deployments.DeploymentPage.waitForInitializingToStop();
                }
            }

            createDeployment(true);

            browser.sleep(3000);
            expect(browser.getCurrentUrl()).toContain('/deployment/', 'DEPLOY_REDIRECT should redirect to deployments');

            components.ui.layout.goToBlueprints();

            createDeployment(false);

            expect(components.ui.common.Dialog.getErrorMessage()).toContain('already', 'CFY-2902 duplicate name should cause error');
            components.ui.common.Dialog.close();

            components.ui.layout.goToDeployments();
            components.ui.deployments.IndexPage.deleteDeployment({id:newDeploymentName});

            browser.sleep(1000).then(done);
        });

        it('should show deploy dialog after default button pressed', function (done) {

            logger.trace('start create deployment with default button test');

            components.ui.blueprints.IndexPage.createDeploymentWithDefaultBtn({'name' : testConf.blueprints.blueprintToDeploy});
            expect(element(by.css('#deployDialogContainer')).isPresent()).toBe(true);

            browser.sleep(500).then(function(){ done(); });
        });

        it('should upload and delete a blueprint, ENTER_BLUEPRINT_NAME', function(done){
            // todo
            //done();
            var blueprintName = 'uploaded-' + new Date().getTime();
            var uploadedBlueprint = { blueprint_id: blueprintName , 'blueprint_filename' : 'simple-blueprint.yaml', 'blueprint_location':'https://github.com/cloudify-cosmo/cloudify-nodecellar-example/archive/master.zip' };
            components.ui.blueprints.IndexPage.uploadBlueprint();

            components.ui.common.Dialog.clickClose();

            expect(components.ui.blueprints.UploadBlueprintDialog.getUploadForm().isPresent()).toBe(false,'CLOSE_BLUEPRINT_DIALOG dialog should be closed after');
            components.ui.blueprints.IndexPage.uploadBlueprint(); // reopen

            components.ui.blueprints.UploadBlueprintDialog.getBlueprintIdLabel().then(function(label){
                expect(label.toLowerCase()).toContain('blueprint id', 'CFY-2301 label should be blueprint id ...  also verifies dialog is open');
            });
            components.ui.blueprints.UploadBlueprintDialog.setDetails(uploadedBlueprint);
            components.ui.blueprints.UploadBlueprintDialog.submit();

            expect(browser.getCurrentUrl()).toContain(uploadedBlueprint.blueprint_id,'UPLOAD_REDIRECT upload should redirect to blueprint when finished');
            //components.ui.blueprints.IndexPage.waitForUploadDone({name: uploadedBlueprint.blueprint_id});
            components.ui.layout.goToBlueprints();

            components.ui.blueprints.IndexPage.deleteBlueprint({name:blueprintName});
            expect(components.ui.blueprints.IndexPage.getBlueprint({name: blueprintName }, true)).toBeUndefined('CFY-3524 delete blueprint should refresh blueprints');

            browser.sleep(1000).then(function(){ done(); });
        });

    });

    describe('topology view', function(){
        it('should open node details panel when clicking on node in topology section', function(done) {
            logger.trace('start blueprint topology section test');
            components.ui.blueprints.IndexPage.goToBlueprint({'name' : testConf.blueprints.blueprintToRead});
            components.ui.blueprints.BlueprintPage.goToTopology();
            components.ui.blueprints.BlueprintPage.Topology.clickNode(testConf.blueprints.nodeToClick);
            components.ui.blueprints.BlueprintPage.PropertiesPanel.waitForVisibility();
            components.ui.blueprints.BlueprintPage.PropertiesPanel.goToProperties();
            components.ui.blueprints.BlueprintPage.PropertiesPanel.goToRelationships();
            components.ui.blueprints.BlueprintPage.PropertiesPanel.close();

            browser.sleep(1000).then(function(){ done(); });
        });
    });

    //it('should open node details panel when clicking on node in network section', function (done) {
    //    logger.trace('start blueprint network section test');
    //    components.ui.blueprints.IndexPage.goToBlueprint({'name' : DEPLOY_BLUEPRINT_NAME});
    //    components.ui.blueprints.BlueprintPage.goToSection('Network');
    //    components.ui.blueprints.BlueprintPage.Network.getSubnets();
    //    components.ui.blueprints.BlueprintPage.Network.getRouters();
    //
    //    browser.sleep(1000).then(function(){ done(); });
    //});
    //
    //it('should open node details panel when clicking on node in nodes section', function (done) {
    //    logger.trace('start blueprint node section test');
    //    components.ui.blueprints.IndexPage.goToBlueprint({'name' : DEPLOY_BLUEPRINT_NAME});
    //    components.ui.blueprints.BlueprintPage.goToSection('Nodes');
    //    components.ui.blueprints.BlueprintPage.NodesTable.clickNode('mongod');
    //
    //    browser.sleep(1000).then(function(){ done(); });
    //});

});
<|endoftext|>


<commit_before>// DATA
import { UNDER13_CARE_EXPENSES, OVER12_CARE_EXPENSES } from '../../data/massachusetts/name-cores';
import { SNAPData } from '../../data/federal/2017/SNAPData';
import { federalPovertyGuidelines } from '../../data/federal/federalPovertyGuidelines';

// LOGIC/UTILITIES
import {
  sumProps,
  getGrossUnearnedIncomeMonthly
} from '../../utils/cashflow';
import {
  getYearlyLimitBySize,
  getMonthlyLimitBySize
} from '../../utils/getGovData';
import {
  getEveryMemberOfHousehold,
  isDisabled,
  isUnder13,
  getUnder13OfHousehold
} from '../../utils/getMembers';

/** Based on https://www.masslegalservices.org/SNAPCalculator */
const getSNAPBenefits = function ( client, timeframe ) {

  client = client[ timeframe ];

  var finalResult = 0,
      grossIncomeTestResult   = hlp.getGrossIncomeTestResult( client ),
      netIncomeTestResult     = hlp.getNetIncomeTestResult( client ),
      maxSnapAllotment        = getYearlyLimitBySize( SNAPData.SNAP_LIMITS, hlp.householdSize( client ) ),
      thirtyPercentNetIncome  = hlp.monthlyNetIncome(client) * SNAPData.PERCENT_OF_NET,
      maxClientAllotment      = maxSnapAllotment - thirtyPercentNetIncome;

  if (grossIncomeTestResult === true &&  netIncomeTestResult === true) {

    if ( maxClientAllotment <= SNAPData.SMALL_HOUSEHOLD_MIN_GRANT ) {
      if ( hlp.householdSize( client ) <= SNAPData.SMALL_HOUSEHOLD_SIZE) {
        finalResult = SNAPData.SMALL_HOUSEHOLD_MIN_GRANT;
      } else {
        finalResult = 0;
      }

    } else {
      finalResult = maxClientAllotment;
    }

  }

  return finalResult;
}; // End getSNAPBenefits()

// ======================
// BENEFIT HELPER FUNCTIONS
var SNAPhelpers = {},
    hlp         = SNAPhelpers;

// ======================
// HOUSEHOLD/HOUSEHOLD MEMBERS
hlp.householdSize = function ( client ) {
  return client.household.length;
};

hlp.isElderlyOrDisabled = function ( member ) {
  return member.age >= 60 || isDisabled( member );
};

hlp.hasDisabledOrElderlyMember = function (client) {
  return getEveryMemberOfHousehold( client, hlp.isElderlyOrDisabled ).length > 0;
};

hlp.isDependentOver12 = function ( member ) {
  return (!isUnder13( member ) && member.age <= 18) || isDisabled( member );
};

// ======================
//GROSS INCOME
hlp.getChildSupportPaid = function (client) {
  return client.childSupportPaidOut;
};

hlp.getAdjustedGross = function (client) {
  return client.earned + getGrossUnearnedIncomeMonthly(client) - client.childSupportPaidOut;
};

hlp.getPovertyGrossIncomeLevel = function (client ) {
  return getMonthlyLimitBySize(federalPovertyGuidelines, hlp.householdSize( client ), 200);
};

hlp.getGrossIncomeTestResult = function (client) {
  var adjustedGross           = hlp.getAdjustedGross(client),
      povertyGrossIncomeLevel = hlp.getPovertyGrossIncomeLevel(client),
      isPassGrossIncomeTest   = null;
  if ( hlp.hasDisabledOrElderlyMember(client) ) {
    isPassGrossIncomeTest = true;
  } else {
    /** @todo must double checked in the documentation.
     *     Two different results in both excel calculator
     *     and website calculator */
    // minor difference "<" in website calculator logic on line 469.
    if ( adjustedGross <= povertyGrossIncomeLevel ) {
      isPassGrossIncomeTest = true;
    } else {
      isPassGrossIncomeTest = false;
    }
  }
  return isPassGrossIncomeTest;
};

// ======================
// SHELTER
hlp.isHomeless = function(client ) {
  // Worth abstracting, used a few places and may change
  return client.shelter === 'homeless';
};

/** @todo: What about housing voucher? */
hlp.getNonUtilityCosts = function(client) {
  var shelterCost = null,
      isHomeowner = client.shelter === 'homeowner';

  if ( hlp.isHomeless(client) ) {
    shelterCost = 0;
  } else if(isHomeowner) {
    shelterCost = client.mortgage + client.housingInsurance + client.propertyTax;
  } else {
    shelterCost = client.rent;
  }

  return shelterCost;
};

hlp.getUtilityCostByBracket = function (client) {

  if( hlp.isHomeless(client) ){
    return 0;

  } else {

    var utilityCategory = null;

    if ( client.climateControl || client.hasFuelAssistance ) {
      utilityCategory = "Heating";
    } else if ( client.nonHeatElectricity ) {
      utilityCategory = "Non-heating";
    } else if ( client.phone ) {
      utilityCategory = "Telephone";
    } else {
      utilityCategory = "Zero Utility Expenses";
    }

    return SNAPData.UTILITY_COST_BRACKETS[ utilityCategory ];
  }
};

hlp.getTotalshelterCost = function (client) {

  var shelterCosts = hlp.getNonUtilityCosts(client),
      utilityCosts = hlp.getUtilityCostByBracket(client);

  return shelterCosts + utilityCosts;
};

// ======================
// INCOME DEDUCTIONS
hlp.getStandardDeduction = function (client) {
  return getYearlyLimitBySize(SNAPData.STANDARD_DEDUCTIONS, hlp.householdSize( client ));
};

hlp.getEarnedIncomeDeduction = function (client) {
  var totalMonthlyEarned = client.earned;
  return totalMonthlyEarned * SNAPData.PERCENT_GROSS_MONTHLY_EARNED;
};

hlp.getMedicalDeduction = function (client) {
  var medicalDeduce = 0;

  if ( hlp.hasDisabledOrElderlyMember(client) === true ) {
    /** @todo Add disabledAssistance too. Also, otherMedical? */
    var medicalExpenses = client.disabledMedical;
    if ((medicalExpenses >= SNAPData.MIN_MEDICAL_EXPENSES) && (medicalExpenses <= SNAPData.MAX_MEDICAL_EXPENSES)) {
      medicalDeduce = SNAPData.STANDARD_MEDICAL_DEDUCTION;

    } else if (medicalExpenses >= SNAPData.MAX_MEDICAL_EXPENSES++) {
      medicalDeduce = medicalExpenses - SNAPData.MIN_MEDICAL_EXPENSES;

    }
  }  // end if has disabled or elderly

  return medicalDeduce;
};

hlp.getDependentCareDeduction = function (client) {

  var dependentCare = 0;

  /** @todo Adopt https://github.com/codeforboston/cliff-effects/issues/264
   *     model for all these 'kinds' of 'if' situations. If possible. */
  if ( getUnder13OfHousehold( client ).length > 0 ) {
    dependentCare += sumProps( client, UNDER13_CARE_EXPENSES );
  }

  if ( getEveryMemberOfHousehold( client, hlp.isDependentOver12 ).length > 0 ) {
    dependentCare += sumProps( client, OVER12_CARE_EXPENSES );
  }

  return dependentCare;
};

hlp.getHalfAdjustedIncome = function(client) {
  return hlp.getAdjustedIncome(client) * 0.50;
};

hlp.getRawShelterDeduction = function(client) {
  var totalShelterDeduction = 0,
      totalshelterCost      = hlp.getTotalshelterCost(client),
      halfAdjustedIncome    = hlp.getHalfAdjustedIncome(client);

  if ( totalshelterCost - halfAdjustedIncome >= 0   ) {
    totalShelterDeduction = totalshelterCost - halfAdjustedIncome;
  }
  return totalShelterDeduction;
};

hlp.getShelterDeduction = function(client) {

  var rawDeduction = hlp.getRawShelterDeduction(client)

  if ( hlp.hasDisabledOrElderlyMember(client) ) {
    return rawDeduction;
  } else {
    return Math.min( rawDeduction, SNAPData.SHELTER_DEDUCTION_CAP );
  }

};

hlp.getHomelessDeduction = function(client) {
  if ( hlp.isHomeless(client) ) { return SNAPData.HOMELESS_DEDUCTION; }
  else { return 0; }
};

// ======================
// NET INCOME
hlp.getAdjustedIncome = function (client) {
  var adjustedGross           = hlp.getAdjustedGross(client),
      standardDeduction       = hlp.getStandardDeduction(client),
      earnedIncomeDeduction   = hlp.getEarnedIncomeDeduction(client),
      medicalDeduction        = hlp.getMedicalDeduction(client),
      dependentCareDeduction  = hlp.getDependentCareDeduction(client);

  var adjustedIncome = adjustedGross - standardDeduction - earnedIncomeDeduction - medicalDeduction - dependentCareDeduction;
  return Math.max( 0, adjustedIncome );
};

// EXPENSE DEDUCTIONS

// NET INCOME CALCULATION
hlp.monthlyNetIncome = function(client) {
  // May be able to leverage 'getAdjusted...'
  var totalMonthlyEarned        = client.earned,
      earnedIncomeDeduction     = hlp.getEarnedIncomeDeduction(client),
      totalMonthlyUnearnedGross = getGrossUnearnedIncomeMonthly(client),
      standardDeduction         = hlp.getStandardDeduction(client),
      medicalDeduction          = hlp.getMedicalDeduction(client),
      dependentCareDeduction    = hlp.getDependentCareDeduction(client),
      childSupportPaid          = hlp.getChildSupportPaid(client),
      hasHomelessDeduction      = hlp.getHomelessDeduction(client),
      shelterDeduction          = hlp.getShelterDeduction(client);

  var totalIncome     = totalMonthlyEarned      + totalMonthlyUnearnedGross,
      totalDeductions = earnedIncomeDeduction   + standardDeduction + medicalDeduction
                      + hasHomelessDeduction    + shelterDeduction
                      + dependentCareDeduction  + childSupportPaid;
  var afterDeductions = totalIncome - totalDeductions;

  return Math.max( 0, afterDeductions );
};

hlp.getMaxNetIncome = function (client) {
  //TODO: Logic different in website calculate; when (hlp.monthlyNetIncome < 0 ) = 0 while excel return a number
  var adjustedGross           = hlp.getAdjustedGross(client),
      povertyGrossIncomeLevel = hlp.getPovertyGrossIncomeLevel(client),
      disabledOrElderlyMember = hlp.hasDisabledOrElderlyMember(client);

  if ( (adjustedGross <= povertyGrossIncomeLevel) || !disabledOrElderlyMember ) {
    return "no limit";
  } else {
    return getYearlyLimitBySize(SNAPData.NET_INCOME_LIMITS, hlp.householdSize( client ));
  }
};

// NET INCOME TEST RESULT
hlp.getNetIncomeTestResult = function(client ) {

  var maxNetIncome = hlp.getMaxNetIncome(client)

  if ( maxNetIncome === "no limit" ) {
    return true;
  } else if ( hlp.monthlyNetIncome(client) < maxNetIncome ) {
    return true;
  } else {
    return false;
  }

};

/** NOTE: Bay State CAP not included as this prototype only deals with
 *     changes in earned income */

export { getSNAPBenefits, SNAPhelpers };
<commit_msg>More math approach to shelter deductions<commit_after>// DATA
import { UNDER13_CARE_EXPENSES, OVER12_CARE_EXPENSES } from '../../data/massachusetts/name-cores';
import { SNAPData } from '../../data/federal/2017/SNAPData';
import { federalPovertyGuidelines } from '../../data/federal/federalPovertyGuidelines';

// LOGIC/UTILITIES
import {
  sumProps,
  getGrossUnearnedIncomeMonthly
} from '../../utils/cashflow';
import {
  getYearlyLimitBySize,
  getMonthlyLimitBySize
} from '../../utils/getGovData';
import {
  getEveryMemberOfHousehold,
  isDisabled,
  isUnder13,
  getUnder13OfHousehold
} from '../../utils/getMembers';

/** Based on https://www.masslegalservices.org/SNAPCalculator */
const getSNAPBenefits = function ( client, timeframe ) {

  client = client[ timeframe ];

  var finalResult = 0,
      grossIncomeTestResult   = hlp.getGrossIncomeTestResult( client ),
      netIncomeTestResult     = hlp.getNetIncomeTestResult( client ),
      maxSnapAllotment        = getYearlyLimitBySize( SNAPData.SNAP_LIMITS, hlp.householdSize( client ) ),
      thirtyPercentNetIncome  = hlp.monthlyNetIncome(client) * SNAPData.PERCENT_OF_NET,
      maxClientAllotment      = maxSnapAllotment - thirtyPercentNetIncome;

  if (grossIncomeTestResult === true &&  netIncomeTestResult === true) {

    if ( maxClientAllotment <= SNAPData.SMALL_HOUSEHOLD_MIN_GRANT ) {
      if ( hlp.householdSize( client ) <= SNAPData.SMALL_HOUSEHOLD_SIZE) {
        finalResult = SNAPData.SMALL_HOUSEHOLD_MIN_GRANT;
      } else {
        finalResult = 0;
      }

    } else {
      finalResult = maxClientAllotment;
    }

  }

  return finalResult;
}; // End getSNAPBenefits()

// ======================
// BENEFIT HELPER FUNCTIONS
var SNAPhelpers = {},
    hlp         = SNAPhelpers;

// ======================
// HOUSEHOLD/HOUSEHOLD MEMBERS
hlp.householdSize = function ( client ) {
  return client.household.length;
};

hlp.isElderlyOrDisabled = function ( member ) {
  return member.age >= 60 || isDisabled( member );
};

hlp.hasDisabledOrElderlyMember = function (client) {
  return getEveryMemberOfHousehold( client, hlp.isElderlyOrDisabled ).length > 0;
};

hlp.isDependentOver12 = function ( member ) {
  return (!isUnder13( member ) && member.age <= 18) || isDisabled( member );
};

// ======================
//GROSS INCOME
hlp.getChildSupportPaid = function (client) {
  return client.childSupportPaidOut;
};

hlp.getAdjustedGross = function (client) {
  return client.earned + getGrossUnearnedIncomeMonthly(client) - client.childSupportPaidOut;
};

hlp.getPovertyGrossIncomeLevel = function (client ) {
  return getMonthlyLimitBySize(federalPovertyGuidelines, hlp.householdSize( client ), 200);
};

hlp.getGrossIncomeTestResult = function (client) {
  var adjustedGross           = hlp.getAdjustedGross(client),
      povertyGrossIncomeLevel = hlp.getPovertyGrossIncomeLevel(client),
      isPassGrossIncomeTest   = null;
  if ( hlp.hasDisabledOrElderlyMember(client) ) {
    isPassGrossIncomeTest = true;
  } else {
    /** @todo must double checked in the documentation.
     *     Two different results in both excel calculator
     *     and website calculator */
    // minor difference "<" in website calculator logic on line 469.
    if ( adjustedGross <= povertyGrossIncomeLevel ) {
      isPassGrossIncomeTest = true;
    } else {
      isPassGrossIncomeTest = false;
    }
  }
  return isPassGrossIncomeTest;
};

// ======================
// SHELTER
hlp.isHomeless = function(client ) {
  // Worth abstracting, used a few places and may change
  return client.shelter === 'homeless';
};

/** @todo: What about housing voucher? */
hlp.getNonUtilityCosts = function(client) {
  var shelterCost = null,
      isHomeowner = client.shelter === 'homeowner';

  if ( hlp.isHomeless(client) ) {
    shelterCost = 0;
  } else if(isHomeowner) {
    shelterCost = client.mortgage + client.housingInsurance + client.propertyTax;
  } else {
    shelterCost = client.rent;
  }

  return shelterCost;
};

hlp.getUtilityCostByBracket = function (client) {

  if( hlp.isHomeless(client) ){
    return 0;

  } else {

    var utilityCategory = null;

    if ( client.climateControl || client.hasFuelAssistance ) {
      utilityCategory = "Heating";
    } else if ( client.nonHeatElectricity ) {
      utilityCategory = "Non-heating";
    } else if ( client.phone ) {
      utilityCategory = "Telephone";
    } else {
      utilityCategory = "Zero Utility Expenses";
    }

    return SNAPData.UTILITY_COST_BRACKETS[ utilityCategory ];
  }
};

hlp.getTotalshelterCost = function (client) {

  var shelterCosts = hlp.getNonUtilityCosts(client),
      utilityCosts = hlp.getUtilityCostByBracket(client);

  return shelterCosts + utilityCosts;
};

// ======================
// INCOME DEDUCTIONS
hlp.getStandardDeduction = function (client) {
  return getYearlyLimitBySize(SNAPData.STANDARD_DEDUCTIONS, hlp.householdSize( client ));
};

hlp.getEarnedIncomeDeduction = function (client) {
  var totalMonthlyEarned = client.earned;
  return totalMonthlyEarned * SNAPData.PERCENT_GROSS_MONTHLY_EARNED;
};

hlp.getMedicalDeduction = function (client) {
  var medicalDeduce = 0;

  if ( hlp.hasDisabledOrElderlyMember(client) === true ) {
    /** @todo Add disabledAssistance too. Also, otherMedical? */
    var medicalExpenses = client.disabledMedical;
    if ((medicalExpenses >= SNAPData.MIN_MEDICAL_EXPENSES) && (medicalExpenses <= SNAPData.MAX_MEDICAL_EXPENSES)) {
      medicalDeduce = SNAPData.STANDARD_MEDICAL_DEDUCTION;

    } else if (medicalExpenses >= SNAPData.MAX_MEDICAL_EXPENSES++) {
      medicalDeduce = medicalExpenses - SNAPData.MIN_MEDICAL_EXPENSES;

    }
  }  // end if has disabled or elderly

  return medicalDeduce;
};

hlp.getDependentCareDeduction = function (client) {

  var dependentCare = 0;

  /** @todo Adopt https://github.com/codeforboston/cliff-effects/issues/264
   *     model for all these 'kinds' of 'if' situations. If possible. */
  if ( getUnder13OfHousehold( client ).length > 0 ) {
    dependentCare += sumProps( client, UNDER13_CARE_EXPENSES );
  }

  if ( getEveryMemberOfHousehold( client, hlp.isDependentOver12 ).length > 0 ) {
    dependentCare += sumProps( client, OVER12_CARE_EXPENSES );
  }

  return dependentCare;
};

hlp.getHalfAdjustedIncome = function(client) {
  return hlp.getAdjustedIncome(client) * 0.50;
};

hlp.getRawShelterDeduction = function(client) {
  var totalShelterCost    = hlp.getTotalshelterCost(client),
      halfAdjustedIncome  = hlp.getHalfAdjustedIncome(client),
      rawShelterDeduction = totalShelterCost - halfAdjustedIncome;

  return Math.max( 0, rawShelterDeduction );
};

hlp.getShelterDeduction = function(client) {

  var rawDeduction = hlp.getRawShelterDeduction(client)

  if ( hlp.hasDisabledOrElderlyMember(client) ) {
    return rawDeduction;
  } else {
    return Math.min( rawDeduction, SNAPData.SHELTER_DEDUCTION_CAP );
  }

};

hlp.getHomelessDeduction = function(client) {
  if ( hlp.isHomeless(client) ) { return SNAPData.HOMELESS_DEDUCTION; }
  else { return 0; }
};

// ======================
// NET INCOME
hlp.getAdjustedIncome = function (client) {
  var adjustedGross           = hlp.getAdjustedGross(client),
      standardDeduction       = hlp.getStandardDeduction(client),
      earnedIncomeDeduction   = hlp.getEarnedIncomeDeduction(client),
      medicalDeduction        = hlp.getMedicalDeduction(client),
      dependentCareDeduction  = hlp.getDependentCareDeduction(client);

  var adjustedIncome = adjustedGross - standardDeduction - earnedIncomeDeduction - medicalDeduction - dependentCareDeduction;
  return Math.max( 0, adjustedIncome );
};

// EXPENSE DEDUCTIONS

// NET INCOME CALCULATION
hlp.monthlyNetIncome = function(client) {
  // May be able to leverage 'getAdjusted...'
  var totalMonthlyEarned        = client.earned,
      earnedIncomeDeduction     = hlp.getEarnedIncomeDeduction(client),
      totalMonthlyUnearnedGross = getGrossUnearnedIncomeMonthly(client),
      standardDeduction         = hlp.getStandardDeduction(client),
      medicalDeduction          = hlp.getMedicalDeduction(client),
      dependentCareDeduction    = hlp.getDependentCareDeduction(client),
      childSupportPaid          = hlp.getChildSupportPaid(client),
      hasHomelessDeduction      = hlp.getHomelessDeduction(client),
      shelterDeduction          = hlp.getShelterDeduction(client);

  var totalIncome     = totalMonthlyEarned      + totalMonthlyUnearnedGross,
      totalDeductions = earnedIncomeDeduction   + standardDeduction + medicalDeduction
                      + hasHomelessDeduction    + shelterDeduction
                      + dependentCareDeduction  + childSupportPaid;
  var afterDeductions = totalIncome - totalDeductions;

  return Math.max( 0, afterDeductions );
};

hlp.getMaxNetIncome = function (client) {
  //TODO: Logic different in website calculate; when (hlp.monthlyNetIncome < 0 ) = 0 while excel return a number
  var adjustedGross           = hlp.getAdjustedGross(client),
      povertyGrossIncomeLevel = hlp.getPovertyGrossIncomeLevel(client),
      disabledOrElderlyMember = hlp.hasDisabledOrElderlyMember(client);

  if ( (adjustedGross <= povertyGrossIncomeLevel) || !disabledOrElderlyMember ) {
    return "no limit";
  } else {
    return getYearlyLimitBySize(SNAPData.NET_INCOME_LIMITS, hlp.householdSize( client ));
  }
};

// NET INCOME TEST RESULT
hlp.getNetIncomeTestResult = function(client ) {

  var maxNetIncome = hlp.getMaxNetIncome(client)

  if ( maxNetIncome === "no limit" ) {
    return true;
  } else if ( hlp.monthlyNetIncome(client) < maxNetIncome ) {
    return true;
  } else {
    return false;
  }

};

/** NOTE: Bay State CAP not included as this prototype only deals with
 *     changes in earned income */

export { getSNAPBenefits, SNAPhelpers };
<|endoftext|>


<commit_before>/**
 * Defines project context events.
 */
export default class ProjectContextEvent {

  /**
   * Creates a new project context event.
   *
   * @param {String} name to use for the event.
   * @param {String} value emitted in the event.
   */
  constructor(name, value) {

    /**
     * @type {String} Stores the name.
     * @private
     */
    this._name = name;

    /**
     * @type {String} Stores the value.
     * @private
     */
    this._value = value;
  }

  /**
   * @returns {String} name of the event.
   */
  get name() {
    return this._name;
  }

  /**
   * @returns {String} value of the event.
   */
  get value() {
    return this._value;
  }

  /**
   * @returns {string} representation of this enum.
   */
  toString() {
    return `ProjectContextEvent.${this.name}`;
  }
}

// Data events - values

ProjectContextEvent.VALUE = new ProjectContextEvent("VALUE", "value");
ProjectContextEvent.VALUE_CREATED = new ProjectContextEvent("VALUE_CREATED", "valueCreated");
ProjectContextEvent.VALUE_UPDATED = new ProjectContextEvent("VALUE_UPDATED", "valueUpdated");
ProjectContextEvent.VALUE_DELETED = new ProjectContextEvent("VALUE_DELETED", "valueDeleted");

// Data events - value cells

ProjectContextEvent.VALUE_CELL = new ProjectContextEvent("VALUE_CELL", "valueCell");
ProjectContextEvent.VALUE_CELL_CREATED = new ProjectContextEvent("VALUE_CELL_CREATED", "valueCellCreated");
ProjectContextEvent.VALUE_CELL_MOVED = new ProjectContextEvent("VALUE_CELL_MOVED", "valueCellMoved");
ProjectContextEvent.VALUE_CELL_DELETED = new ProjectContextEvent("VALUE_CELL_DELETED", "valueCellDeleted");

// // Data events - marker cells
ProjectContextEvent.MARKER_CELL = new ProjectContextEvent("MARKER_CELL", "markerCell");
ProjectContextEvent.MARKER_CELL_CREATED = new ProjectContextEvent("MARKER_CELL_CREATED", "markerCellCreated");
ProjectContextEvent.MARKER_CELL_UPDATED = new ProjectContextEvent("MARKER_CELL_UPDATED", "markerCellUpdated");
ProjectContextEvent.MARKER_CELL_DELETED = new ProjectContextEvent("MARKER_CELL_DELETED", "markerCellDeleted");

// User events - Connection

ProjectContextEvent.ONLINE_USERS = new ProjectContextEvent("ONLINE_USERS", "onlineUsers");
ProjectContextEvent.ONLINE_MEMBER_JOINED = new ProjectContextEvent("ONLINE_MEMBER_JOINED", "memberJoined");
ProjectContextEvent.ONLINE_MEMBER_LEFT = new ProjectContextEvent("ONLINE_MEMBER_LEFT", "memberLeft");
ProjectContextEvent.ONLINE_GUEST_JOINED = new ProjectContextEvent("ONLINE_GUEST_JOINED", "guestJoined");
ProjectContextEvent.ONLINE_GUEST_LEFT = new ProjectContextEvent("ONLINE_GUEST_JOINED", "guestLeft");

// User events - Interaction

ProjectContextEvent.ONLINE_MEMBER_POSITION_UPDATE = new ProjectContextEvent("ONLINE_MEMBER_POSITION_UPDATE", "positionUpdate");

// Do not allow changes
Object.freeze(ProjectContextEvent);
<commit_msg>Added type for history push notifications.<commit_after>/**
 * Defines project context events.
 */
export default class ProjectContextEvent {

  /**
   * Creates a new project context event.
   *
   * @param {String} name to use for the event.
   * @param {String} value emitted in the event.
   */
  constructor(name, value) {

    /**
     * @type {String} Stores the name.
     * @private
     */
    this._name = name;

    /**
     * @type {String} Stores the value.
     * @private
     */
    this._value = value;
  }

  /**
   * @returns {String} name of the event.
   */
  get name() {
    return this._name;
  }

  /**
   * @returns {String} value of the event.
   */
  get value() {
    return this._value;
  }

  /**
   * @returns {string} representation of this enum.
   */
  toString() {
    return `ProjectContextEvent.${this.name}`;
  }
}

// Data events - values

ProjectContextEvent.VALUE = new ProjectContextEvent("VALUE", "value");
ProjectContextEvent.VALUE_CREATED = new ProjectContextEvent("VALUE_CREATED", "valueCreated");
ProjectContextEvent.VALUE_UPDATED = new ProjectContextEvent("VALUE_UPDATED", "valueUpdated");
ProjectContextEvent.VALUE_DELETED = new ProjectContextEvent("VALUE_DELETED", "valueDeleted");

// Data events - value cells

ProjectContextEvent.VALUE_CELL = new ProjectContextEvent("VALUE_CELL", "valueCell");
ProjectContextEvent.VALUE_CELL_CREATED = new ProjectContextEvent("VALUE_CELL_CREATED", "valueCellCreated");
ProjectContextEvent.VALUE_CELL_MOVED = new ProjectContextEvent("VALUE_CELL_MOVED", "valueCellMoved");
ProjectContextEvent.VALUE_CELL_DELETED = new ProjectContextEvent("VALUE_CELL_DELETED", "valueCellDeleted");

// Data events - value history
ProjectContextEvent.VALUE_HISTORY_ADDED = new ProjectContextEvent("VALUE_HISTORY_ADDED", "valueHistoryAdded");

// Data events - marker cells
ProjectContextEvent.MARKER_CELL = new ProjectContextEvent("MARKER_CELL", "markerCell");
ProjectContextEvent.MARKER_CELL_CREATED = new ProjectContextEvent("MARKER_CELL_CREATED", "markerCellCreated");
ProjectContextEvent.MARKER_CELL_UPDATED = new ProjectContextEvent("MARKER_CELL_UPDATED", "markerCellUpdated");
ProjectContextEvent.MARKER_CELL_DELETED = new ProjectContextEvent("MARKER_CELL_DELETED", "markerCellDeleted");

// User events - Connection
ProjectContextEvent.ONLINE_USERS = new ProjectContextEvent("ONLINE_USERS", "onlineUsers");
ProjectContextEvent.ONLINE_MEMBER_JOINED = new ProjectContextEvent("ONLINE_MEMBER_JOINED", "memberJoined");
ProjectContextEvent.ONLINE_MEMBER_LEFT = new ProjectContextEvent("ONLINE_MEMBER_LEFT", "memberLeft");
ProjectContextEvent.ONLINE_GUEST_JOINED = new ProjectContextEvent("ONLINE_GUEST_JOINED", "guestJoined");
ProjectContextEvent.ONLINE_GUEST_LEFT = new ProjectContextEvent("ONLINE_GUEST_JOINED", "guestLeft");

// User events - Interaction

ProjectContextEvent.ONLINE_MEMBER_POSITION_UPDATE = new ProjectContextEvent("ONLINE_MEMBER_POSITION_UPDATE", "positionUpdate");

// Do not allow changes
Object.freeze(ProjectContextEvent);
<|endoftext|>