| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295 |
- import { generateASCIITable } from './asciiTable';
- // http://stackoverflow.com/questions/986937/how-can-i-get-the-browsers-scrollbar-sizes
- const scrollbarWidth = (function calculateScrollbarWidth() {
- const inner = document.createElement('div');
- inner.style.height = '200px';
- inner.style.width = '100%';
- const outer = document.createElement('div');
- outer.style.boxSizing = 'content-box';
- outer.style.height = '150px';
- outer.style.left = '0px';
- outer.style.overflow = 'hidden';
- outer.style.position = 'absolute';
- outer.style.top = '0px';
- outer.style.width = '200px';
- outer.style.visibility = 'hidden';
- outer.appendChild(inner);
- (document.body || document.documentElement).appendChild(outer);
- const w1 = inner.offsetWidth;
- outer.style.overflow = 'scroll';
- let w2 = inner.offsetWidth;
- if (w1 === w2) {
- w2 = outer.clientWidth;
- }
- (document.body || document.documentElement).removeChild(outer);
- return (w1 - w2);
- }());
- beforeEach(function() {
- const currentSpec = this;
- function hot() {
- return currentSpec.$container.data('handsontable');
- }
- const matchers = {
- toBeInArray() {
- return {
- compare(actual, expected) {
- return {
- pass: Array.isArray(expected) && expected.indexOf(actual) > -1
- };
- }
- };
- },
- toBeFunction() {
- return {
- compare(actual) {
- return {
- pass: typeof actual === 'function'
- };
- }
- };
- },
- toBeAroundValue() {
- return {
- compare(actual, expected, diff) {
- const margin = diff || 1;
- const pass = actual >= expected - margin && actual <= expected + margin;
- let message = `Expected ${actual} to be around ${expected} (between ${expected - margin} and ${expected + margin})`;
- if (!pass) {
- message = `Expected ${actual} NOT to be around ${expected} (between ${expected - margin} and ${expected + margin})`;
- }
- return {
- pass,
- message
- };
- }
- };
- },
- /**
- * The matcher checks if the passed cell element is contained in the table viewport.
- */
- toBeVisibleInViewport() {
- return {
- compare(actual) {
- const viewport = hot().view.wt.wtTable.holder;
- const verticalPosition = actual.offsetTop - viewport.scrollTop + scrollbarWidth + actual.clientHeight;
- const horizontalPosition = actual.offsetLeft - viewport.scrollLeft + scrollbarWidth + actual.clientWidth;
- const pass = verticalPosition < viewport.offsetHeight && verticalPosition > 0
- && horizontalPosition < viewport.offsetWidth && horizontalPosition > 0;
- return {
- pass,
- message: 'Expected the element to be visible in the Handsontable viewport'
- };
- }
- };
- },
- /**
- * The matcher checks if the viewport is scrolled in the way that the cell is visible at the top of the viewport.
- */
- toBeVisibleAtTopOfViewport() {
- return {
- compare(actual) {
- const viewport = hot().view.wt.wtTable.holder;
- const verticalPosition = actual.offsetTop - viewport.scrollTop - 1;
- return {
- pass: verticalPosition === 0,
- message: 'Expected the element to be scrolled to the top of the Handsontable viewport'
- };
- }
- };
- },
- /**
- * The matcher checks if the viewport is scrolled in the way that the cell is visible at the bottom of the viewport.
- */
- toBeVisibleAtBottomOfViewport() {
- return {
- compare(actual) {
- const viewport = hot().view.wt.wtTable.holder;
- const verticalPosition = actual.offsetTop - viewport.scrollTop + scrollbarWidth + actual.clientHeight + 1;
- return {
- pass: verticalPosition === viewport.offsetHeight,
- message: 'Expected the element to be scrolled to the bottom of the Handsontable viewport'
- };
- }
- };
- },
- /**
- * The matcher checks if the viewport is scrolled in the way that the cell is visible on the left of the viewport.
- */
- toBeVisibleAtLeftOfViewport() {
- return {
- compare(actual) {
- const viewport = hot().view.wt.wtTable.holder;
- const horizontalPosition = viewport.scrollLeft - actual.offsetLeft;
- return {
- pass: horizontalPosition === 0,
- message: 'Expected the element to be scrolled to the top of the Handsontable viewport'
- };
- }
- };
- },
- /**
- * The matcher checks if the viewport is scrolled in the way that the cell is visible on the right of the viewport.
- */
- toBeVisibleAtRightOfViewport() {
- return {
- compare(actual) {
- const viewport = hot().view.wt.wtTable.holder;
- const horizontalPosition = viewport.scrollLeft - actual.offsetLeft + actual.clientWidth - scrollbarWidth + 1;
- return {
- pass: horizontalPosition === viewport.offsetWidth,
- message: 'Expected the element to be scrolled to the top of the Handsontable viewport'
- };
- }
- };
- },
- toBeListFulfillingCondition() {
- const redColor = '\x1b[31m';
- const resetColor = '\x1b[0m';
- return {
- compare(checkedArray, conditionFunction) {
- if (typeof conditionFunction !== 'function') {
- throw Error('Parameter passed to `toBeListFulfillingCondition` should be a function.');
- }
- const isListWithValues = Array.isArray(checkedArray) || checkedArray.length > 0;
- const elementNotFulfillingCondition = checkedArray.find(element => !conditionFunction(element));
- const containsUndefined = isListWithValues && checkedArray.includes(undefined);
- const pass = isListWithValues && !containsUndefined && elementNotFulfillingCondition === undefined;
- let message;
- if (!isListWithValues) {
- message = 'Non-empty list should be passed as expect parameter.';
- } else if (containsUndefined) {
- message = `List ${redColor}${checkedArray.join(', ')}${resetColor} contains ${redColor}undefined${resetColor} value.`;
- } else if (elementNotFulfillingCondition !== undefined) {
- let entityValue = elementNotFulfillingCondition;
- if (typeof elementNotFulfillingCondition === 'string') {
- entityValue = `"${elementNotFulfillingCondition}"`;
- }
- message = `Entity ${redColor}${entityValue}${resetColor}, from list: ${redColor}${checkedArray.join(', ')}${resetColor} doesn't satisfy the condition.`;
- }
- return {
- pass,
- message
- };
- }
- };
- },
- /**
- * The matcher checks if the provided selection pattern matches to the rendered cells by checking if
- * the appropriate CSS class name was added.
- *
- * The provided structure should be passed as an array of arrays, for instance:
- * ```
- * // Non-contiguous selection (with enabled top and left headers)
- * expect(`
- * | ║ : : * : * |
- * |===:===:===:===:===|
- * | - ║ : : A : 0 |
- * | - ║ : 1 : 0 : 0 |
- * | - ║ : 2 : 1 : 0 |
- * | - ║ : 2 : 1 : 0 |
- * | - ║ : 1 : 1 : 0 |
- * | - ║ : : 0 : 0 |
- * `).toBeMatchToSelectionPattern();
- * // Single cell selection (with fixedRowsTop: 1 and fixedColumnsLeft: 2)
- * expect(`
- * | : | : : |
- * |---:---:---:---:---|
- * | : | : : |
- * | : | : : |
- * | : | # : : |
- * | : | : : |
- * | : | : : |
- * | : | : : |
- * `).toBeMatchToSelectionPattern();
- * ```
- *
- * The meaning of the symbol used to describe the cells:
- * ' ' - An empty space indicates cell which doesn't have added any selection classes.
- * '0' - The number (from 0 to 7) indicates selected layer level.
- * 'A' - The letters (from A to H) indicates the position of the cell which contains the hidden editor
- * (which `current` class name). The letter `A` indicates the currently selected cell with
- * a background of the first layer and `H` as the latest layer (most dark).
- * '#' - The hash symbol indicates the currently selected cell without changed background color.
- *
- * The meaning of the symbol used to describe the table:
- * ':' - Column separator (only for better visual looks).
- * '║' - This symbol separates the row headers from the table content.
- * '===' - This symbol separates the column headers from the table content.
- * '|' - The symbol which indicates the left overlay edge.
- * '---' - The symbol which indicates the top overlay edge.
- */
- toBeMatchToSelectionPattern() {
- return {
- compare(actualPattern) {
- const asciiTable = generateASCIITable(hot().rootElement);
- const patternParts = (actualPattern || '').split(/\n/);
- const redundantPadding = patternParts.reduce((padding, line) => {
- const trimmedLine = line.trim();
- let nextPadding = padding;
- if (trimmedLine) {
- const currentPadding = line.search(/\S|$/);
- if (currentPadding < nextPadding) {
- nextPadding = currentPadding;
- }
- }
- return nextPadding;
- }, Infinity);
- const normalizedPattern = patternParts.reduce((acc, line) => {
- const trimmedLine = line.trim();
- if (trimmedLine) {
- acc.push(line.substr(redundantPadding));
- }
- return acc;
- }, []);
- const actualAsciiTable = normalizedPattern.join('\n');
- const message = `Expected the pattern selection \n${actualAsciiTable}\nto match to the visual state of the rendered selection \n${asciiTable}\n`;
- return {
- pass: asciiTable === actualAsciiTable,
- message,
- };
- }
- };
- },
- };
- jasmine.addMatchers(matchers);
- });
|