diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 00000000..aa300e5d
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,75 @@
+name: Tests
+
+on: [push]
+
+jobs:
+ unit:
+ name: Unit tests
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Use Node.js
+ uses: actions/setup-node@v1
+ with:
+ node-version: '12.x'
+ - name: Install dependencies
+ run: yarn --frozen-lockfile --non-interactive --silent --ignore-scripts
+ - name: Run Jest
+ uses: tangro/actions-test@1.1.0
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GITHUB_CONTEXT: ${{ toJson(github) }}
+ with:
+ command: test-ci
+
+ static-analysis:
+ name: Static analysis
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Use Node.js
+ uses: actions/setup-node@v1
+ with:
+ node-version: '12.x'
+ - name: Install dependencies
+ run: yarn --frozen-lockfile --non-interactive --silent --ignore-scripts
+ - name: Run ESLint
+ run: yarn lint
+
+ integration-cli:
+ name: 'Integration tests: CLI'
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Use Node.js
+ uses: actions/setup-node@v1
+ with:
+ node-version: '12.x'
+ - name: Install dependencies
+ run: yarn --frozen-lockfile --non-interactive --silent --ignore-scripts
+ - name: Run integration tests
+ run: yarn workspace @loki/test-cli test
+
+ visual-react-dom:
+ name: 'Visual tests: React DOM'
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Use Node.js
+ uses: actions/setup-node@v1
+ with:
+ node-version: '12.x'
+ - name: Install dependencies
+ run: yarn --frozen-lockfile --non-interactive --silent --ignore-scripts
+ - name: Run loki
+ run: yarn workspace @loki/example-react test-ci
+ - name: Archive screenshots
+ if: ${{ failure() }}
+ uses: actions/upload-artifact@v1
+ with:
+ name: screenshots
+ path: examples/react/.loki
diff --git a/.gitignore b/.gitignore
index 2259c16e..cbcfa59f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@ yarn-error.log
_book
.loki
!examples/*/.loki
+test_results.json
diff --git a/docs/configuration.md b/docs/configuration.md
index b3f836ae..f5c486d2 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -38,7 +38,7 @@ Example `package.json`:
}
```
-You may also use a file named `.lokirc`, `.lokirc.json` or `loki.config.js` if you don't want to pollute your `package.json`.
+You may also use a file named `.lokirc`, `.lokirc.json` or `loki.config.js` (see the react example) if you don't want to pollute your `package.json`.
## `chromeSelector`
diff --git a/examples/react/.loki/reference/chrome_a4_FocusedInput_default.png b/examples/react/.loki/reference/chrome_a4_FocusedInput_default.png
new file mode 100644
index 00000000..4bebfe7f
Binary files /dev/null and b/examples/react/.loki/reference/chrome_a4_FocusedInput_default.png differ
diff --git a/examples/react/.loki/reference/chrome_a4_Positions_Absolute.png b/examples/react/.loki/reference/chrome_a4_Positions_Absolute.png
new file mode 100644
index 00000000..a4f3a43b
Binary files /dev/null and b/examples/react/.loki/reference/chrome_a4_Positions_Absolute.png differ
diff --git a/examples/react/.loki/reference/chrome_a4_Positions_Fixed.png b/examples/react/.loki/reference/chrome_a4_Positions_Fixed.png
new file mode 100644
index 00000000..4b764dd3
Binary files /dev/null and b/examples/react/.loki/reference/chrome_a4_Positions_Fixed.png differ
diff --git a/examples/react/.loki/reference/chrome_a4_Positions_Nested.png b/examples/react/.loki/reference/chrome_a4_Positions_Nested.png
new file mode 100644
index 00000000..2cbc48fe
Binary files /dev/null and b/examples/react/.loki/reference/chrome_a4_Positions_Nested.png differ
diff --git a/examples/react/.loki/reference/chrome_a4_Positions_Nested_Overflow_Hidden.png b/examples/react/.loki/reference/chrome_a4_Positions_Nested_Overflow_Hidden.png
new file mode 100644
index 00000000..712da5f8
Binary files /dev/null and b/examples/react/.loki/reference/chrome_a4_Positions_Nested_Overflow_Hidden.png differ
diff --git a/examples/react/.loki/reference/chrome_a4_Positions_Overflow_Hidden_Fixed.png b/examples/react/.loki/reference/chrome_a4_Positions_Overflow_Hidden_Fixed.png
new file mode 100644
index 00000000..50bb756e
Binary files /dev/null and b/examples/react/.loki/reference/chrome_a4_Positions_Overflow_Hidden_Fixed.png differ
diff --git a/examples/react/.loki/reference/chrome_a4_Positions_Overflow_Hidden_Relative.png b/examples/react/.loki/reference/chrome_a4_Positions_Overflow_Hidden_Relative.png
new file mode 100644
index 00000000..61c32402
Binary files /dev/null and b/examples/react/.loki/reference/chrome_a4_Positions_Overflow_Hidden_Relative.png differ
diff --git a/examples/react/.loki/reference/chrome_a4_Positions_Relative_Wrapper.png b/examples/react/.loki/reference/chrome_a4_Positions_Relative_Wrapper.png
new file mode 100644
index 00000000..192ab29e
Binary files /dev/null and b/examples/react/.loki/reference/chrome_a4_Positions_Relative_Wrapper.png differ
diff --git a/examples/react/.loki/reference/chrome_a4_Positions_Slider_With_Nested_Overflow.png b/examples/react/.loki/reference/chrome_a4_Positions_Slider_With_Nested_Overflow.png
new file mode 100644
index 00000000..d2116b9e
Binary files /dev/null and b/examples/react/.loki/reference/chrome_a4_Positions_Slider_With_Nested_Overflow.png differ
diff --git a/examples/react/.loki/reference/chrome_iphone7_FocusedInput_default.png b/examples/react/.loki/reference/chrome_iphone7_FocusedInput_default.png
new file mode 100644
index 00000000..68ded097
Binary files /dev/null and b/examples/react/.loki/reference/chrome_iphone7_FocusedInput_default.png differ
diff --git a/examples/react/.loki/reference/chrome_iphone7_Positions_Absolute.png b/examples/react/.loki/reference/chrome_iphone7_Positions_Absolute.png
new file mode 100644
index 00000000..2b0d6a43
Binary files /dev/null and b/examples/react/.loki/reference/chrome_iphone7_Positions_Absolute.png differ
diff --git a/examples/react/.loki/reference/chrome_iphone7_Positions_Fixed.png b/examples/react/.loki/reference/chrome_iphone7_Positions_Fixed.png
new file mode 100644
index 00000000..9a7fd1ee
Binary files /dev/null and b/examples/react/.loki/reference/chrome_iphone7_Positions_Fixed.png differ
diff --git a/examples/react/.loki/reference/chrome_iphone7_Positions_Nested.png b/examples/react/.loki/reference/chrome_iphone7_Positions_Nested.png
new file mode 100644
index 00000000..a84ec26b
Binary files /dev/null and b/examples/react/.loki/reference/chrome_iphone7_Positions_Nested.png differ
diff --git a/examples/react/.loki/reference/chrome_iphone7_Positions_Nested_Overflow_Hidden.png b/examples/react/.loki/reference/chrome_iphone7_Positions_Nested_Overflow_Hidden.png
new file mode 100644
index 00000000..517dd177
Binary files /dev/null and b/examples/react/.loki/reference/chrome_iphone7_Positions_Nested_Overflow_Hidden.png differ
diff --git a/examples/react/.loki/reference/chrome_iphone7_Positions_Overflow_Hidden_Fixed.png b/examples/react/.loki/reference/chrome_iphone7_Positions_Overflow_Hidden_Fixed.png
new file mode 100644
index 00000000..0bc73baf
Binary files /dev/null and b/examples/react/.loki/reference/chrome_iphone7_Positions_Overflow_Hidden_Fixed.png differ
diff --git a/examples/react/.loki/reference/chrome_iphone7_Positions_Overflow_Hidden_Relative.png b/examples/react/.loki/reference/chrome_iphone7_Positions_Overflow_Hidden_Relative.png
new file mode 100644
index 00000000..c572f07e
Binary files /dev/null and b/examples/react/.loki/reference/chrome_iphone7_Positions_Overflow_Hidden_Relative.png differ
diff --git a/examples/react/.loki/reference/chrome_iphone7_Positions_Relative_Wrapper.png b/examples/react/.loki/reference/chrome_iphone7_Positions_Relative_Wrapper.png
new file mode 100644
index 00000000..308d1683
Binary files /dev/null and b/examples/react/.loki/reference/chrome_iphone7_Positions_Relative_Wrapper.png differ
diff --git a/examples/react/.loki/reference/chrome_iphone7_Positions_Slider_With_Nested_Overflow.png b/examples/react/.loki/reference/chrome_iphone7_Positions_Slider_With_Nested_Overflow.png
new file mode 100644
index 00000000..b9dc62b1
Binary files /dev/null and b/examples/react/.loki/reference/chrome_iphone7_Positions_Slider_With_Nested_Overflow.png differ
diff --git a/examples/react/.loki/reference/chrome_laptop_FocusedInput_default.png b/examples/react/.loki/reference/chrome_laptop_FocusedInput_default.png
new file mode 100644
index 00000000..4bebfe7f
Binary files /dev/null and b/examples/react/.loki/reference/chrome_laptop_FocusedInput_default.png differ
diff --git a/examples/react/.loki/reference/chrome_laptop_Positions_Absolute.png b/examples/react/.loki/reference/chrome_laptop_Positions_Absolute.png
new file mode 100644
index 00000000..a4f3a43b
Binary files /dev/null and b/examples/react/.loki/reference/chrome_laptop_Positions_Absolute.png differ
diff --git a/examples/react/.loki/reference/chrome_laptop_Positions_Fixed.png b/examples/react/.loki/reference/chrome_laptop_Positions_Fixed.png
new file mode 100644
index 00000000..4b764dd3
Binary files /dev/null and b/examples/react/.loki/reference/chrome_laptop_Positions_Fixed.png differ
diff --git a/examples/react/.loki/reference/chrome_laptop_Positions_Nested.png b/examples/react/.loki/reference/chrome_laptop_Positions_Nested.png
new file mode 100644
index 00000000..2cbc48fe
Binary files /dev/null and b/examples/react/.loki/reference/chrome_laptop_Positions_Nested.png differ
diff --git a/examples/react/.loki/reference/chrome_laptop_Positions_Nested_Overflow_Hidden.png b/examples/react/.loki/reference/chrome_laptop_Positions_Nested_Overflow_Hidden.png
new file mode 100644
index 00000000..712da5f8
Binary files /dev/null and b/examples/react/.loki/reference/chrome_laptop_Positions_Nested_Overflow_Hidden.png differ
diff --git a/examples/react/.loki/reference/chrome_laptop_Positions_Overflow_Hidden_Fixed.png b/examples/react/.loki/reference/chrome_laptop_Positions_Overflow_Hidden_Fixed.png
new file mode 100644
index 00000000..50bb756e
Binary files /dev/null and b/examples/react/.loki/reference/chrome_laptop_Positions_Overflow_Hidden_Fixed.png differ
diff --git a/examples/react/.loki/reference/chrome_laptop_Positions_Overflow_Hidden_Relative.png b/examples/react/.loki/reference/chrome_laptop_Positions_Overflow_Hidden_Relative.png
new file mode 100644
index 00000000..61c32402
Binary files /dev/null and b/examples/react/.loki/reference/chrome_laptop_Positions_Overflow_Hidden_Relative.png differ
diff --git a/examples/react/.loki/reference/chrome_laptop_Positions_Relative_Wrapper.png b/examples/react/.loki/reference/chrome_laptop_Positions_Relative_Wrapper.png
new file mode 100644
index 00000000..192ab29e
Binary files /dev/null and b/examples/react/.loki/reference/chrome_laptop_Positions_Relative_Wrapper.png differ
diff --git a/examples/react/.loki/reference/chrome_laptop_Positions_Slider_With_Nested_Overflow.png b/examples/react/.loki/reference/chrome_laptop_Positions_Slider_With_Nested_Overflow.png
new file mode 100644
index 00000000..d2116b9e
Binary files /dev/null and b/examples/react/.loki/reference/chrome_laptop_Positions_Slider_With_Nested_Overflow.png differ
diff --git a/examples/react/loki.config.js b/examples/react/loki.config.js
new file mode 100644
index 00000000..c057915a
--- /dev/null
+++ b/examples/react/loki.config.js
@@ -0,0 +1,20 @@
+module.exports = {
+ "chromeSelector": ".wrapper > *, #root > *",
+ "diffingEngine": "looks-same",
+ "configurations": {
+ "chrome.laptop": {
+ "target": "chrome.docker",
+ "width": 1366,
+ "height": 768
+ },
+ "chrome.iphone7": {
+ "target": "chrome.docker",
+ "preset": "iPhone 7"
+ },
+ "chrome.a4": {
+ "target": "chrome.docker",
+ "preset": "A4 Paper"
+ }
+ },
+ "fetchFailIgnore": "localhost:1234/get"
+};
diff --git a/examples/react/package.json b/examples/react/package.json
index ee69be2c..17429c5a 100644
--- a/examples/react/package.json
+++ b/examples/react/package.json
@@ -29,26 +29,6 @@
"@storybook/react": "^5.3.13",
"loki": "^0.20.3"
},
- "loki": {
- "chromeSelector": ".wrapper > *, #root > *",
- "diffingEngine": "looks-same",
- "configurations": {
- "chrome.laptop": {
- "target": "chrome.docker",
- "width": 1366,
- "height": 768
- },
- "chrome.iphone7": {
- "target": "chrome.docker",
- "preset": "iPhone 7"
- },
- "chrome.a4": {
- "target": "chrome.docker",
- "preset": "A4 Paper"
- }
- },
- "fetchFailIgnore": "localhost:1234/get"
- },
"eslintConfig": {
"extends": "react-app"
},
diff --git a/examples/react/src/FocusedInput.js b/examples/react/src/FocusedInput.js
new file mode 100644
index 00000000..7a25ff6a
--- /dev/null
+++ b/examples/react/src/FocusedInput.js
@@ -0,0 +1,5 @@
+import React from 'react';
+
+const FocusedInput = () => ;
+
+export default FocusedInput;
diff --git a/examples/react/src/stories/Positions.stories.js b/examples/react/src/stories/Positions.stories.js
new file mode 100644
index 00000000..31f7e6d5
--- /dev/null
+++ b/examples/react/src/stories/Positions.stories.js
@@ -0,0 +1,278 @@
+import React from 'react';
+
+export default {
+ title: 'Positions',
+};
+
+export const Absolute = () => (
+
+
+ Position Absolute
+
+
+);
+
+export const RelativeWrapper = () => (
+
+
+ Position Absolute
+
+
+ Position Fixed
+
+
+);
+
+export const Fixed = () => (
+
+);
+
+export const Nested = () => (
+
+
+ Position Absolute
+
+ Position Fixed
+
+
+
+);
+
+export const OverflowHiddenRelative = () => (
+
+);
+
+export const OverflowHiddenFixed = () => (
+
+);
+
+export const SliderWithNestedOverflow = () => (
+
+
+
+
+
+ {[...new Array(10)].map((_, index) => (
+
+ {index + 1}
+
+ ))}
+
+
+
+
+
+);
+
+export const NestedOverflowHidden = () => (
+
+ The other box should not be visible
+
+
+);
diff --git a/examples/react/src/stories/index.stories.js b/examples/react/src/stories/index.stories.js
index abd92ad7..1df69a7e 100644
--- a/examples/react/src/stories/index.stories.js
+++ b/examples/react/src/stories/index.stories.js
@@ -16,6 +16,7 @@ import NonIntViewport from '../NonIntViewport';
import FetchComponent from '../FetchComponent';
import ZeroHeightWithPadding from '../ZeroHeightWithPadding';
import Hover from '../Hover';
+import FocusedInput from '../FocusedInput';
storiesOf('Welcome', module)
.lokiSkip('to Storybook', () => )
@@ -75,3 +76,5 @@ storiesOf('Zero height', module).add('with padding', () => (
));
storiesOf('Hover', module).add('default', () => );
+
+storiesOf('FocusedInput', module).add('default', () => );
diff --git a/package.json b/package.json
index cb71f59c..98ff23b8 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,8 @@
"scripts": {
"format": "prettier '{,packages/**/,examples/**/,website/**/,docs/**/,test/**/}*.{md,js,json}' --write",
"lint": "eslint packages/*/src",
- "test": "jest"
+ "test": "jest",
+ "test-ci": "jest --testLocationInResults --ci --outputFile=test_results.json --json"
},
"engines": {
"node": ">=7.6"
@@ -58,7 +59,7 @@
"testPathIgnorePatterns": [
"/node_modules/",
"/examples/",
- "test/cli/generated"
+ "/test/cli/generated/"
]
}
}
diff --git a/packages/browser/src/disable-input-caret.js b/packages/browser/src/disable-input-caret.js
new file mode 100644
index 00000000..1cfd5f83
--- /dev/null
+++ b/packages/browser/src/disable-input-caret.js
@@ -0,0 +1,16 @@
+const disableInputCaret = window => {
+ const DISABLE_INPUT_CARET_STYLE = `
+ * {
+ caret-color: transparent !important;
+ }
+ `;
+
+ // Make blinking input carets transparent to avoid flakiness.
+ window.document.addEventListener('DOMContentLoaded', () => {
+ const styleElement = window.document.createElement('style');
+ window.document.documentElement.appendChild(styleElement);
+ styleElement.sheet.insertRule(DISABLE_INPUT_CARET_STYLE);
+ });
+};
+
+module.exports = disableInputCaret;
diff --git a/packages/browser/src/get-selector-box-size.js b/packages/browser/src/get-selector-box-size.js
index 86013d31..0b46aedc 100644
--- a/packages/browser/src/get-selector-box-size.js
+++ b/packages/browser/src/get-selector-box-size.js
@@ -1,14 +1,84 @@
const getSelectorBoxSize = (window, selector) => {
- const isNotWrapperElement = (element, index, array) => {
- const isWrapper = array.some(node =>
- node === element ? false : element.contains(node)
- );
- return !isWrapper;
- };
+ function hasOverflow(element) {
+ const overflowValues = ['auto', 'hidden', 'scroll'];
+ const style = window.getComputedStyle(element);
+
+ if (
+ overflowValues.includes(style.overflowY) ||
+ overflowValues.includes(style.overflowX) ||
+ overflowValues.includes(style.overflow)
+ ) {
+ return true;
+ }
+
+ return false;
+ }
- const isVisisble = element => {
+ function hasFixedPosition(element) {
const style = window.getComputedStyle(element);
+ return style.position === 'fixed';
+ }
+
+ function isElementHiddenByOverflow(
+ element,
+ { hasParentFixedPosition, hasParentOverflowHidden, parentNotVisible }
+ ) {
+ const isElementOutOfBounds = () => {
+ try {
+ const elementRect = element.getBoundingClientRect();
+ const containerRect = hasParentOverflowHidden.getBoundingClientRect();
+ const top = elementRect.top < containerRect.top;
+ const bottom = elementRect.bottom > containerRect.bottom;
+ const left = elementRect.left < containerRect.left;
+ const right = elementRect.right > containerRect.right;
+ return top || bottom || left || right;
+ } catch (e) {
+ return false;
+ }
+ };
+
+ // Has fixed so it should always be visible
+ if (hasFixedPosition(element)) {
+ return false;
+ }
+ // Element is not fixed and parent is hidden by overflow
+ // So this should not be visible
+ if (parentNotVisible) {
+ return true;
+ }
+
+ // Parent has fixed and overflow hidden
+ // check if its out of bounds
+ if (
+ hasParentFixedPosition &&
+ hasParentOverflowHidden &&
+ hasParentFixedPosition === hasParentOverflowHidden
+ ) {
+ return isElementOutOfBounds();
+ }
+
+ // If we have a fixed element deeper then overflow
+ // We know the element is visible
+ if (
+ hasParentFixedPosition &&
+ hasParentOverflowHidden &&
+ hasParentOverflowHidden !== hasParentFixedPosition &&
+ hasParentOverflowHidden.contains(hasParentFixedPosition)
+ ) {
+ return false;
+ }
+
+ // Parent has overflow so we need to check if this element is out of bounds
+ if (hasParentOverflowHidden) {
+ return isElementOutOfBounds();
+ }
+
+ return false;
+ }
+
+ function isVisible(element) {
+ const style = window.getComputedStyle(element);
return !(
style.visibility === 'hidden' ||
style.display === 'none' ||
@@ -16,11 +86,85 @@ const getSelectorBoxSize = (window, selector) => {
((style.width === '0px' || style.height === '0px') &&
style.padding === '0px')
);
- };
+ }
+
+ const elements = [];
+
+ function walk(
+ element,
+ {
+ isRoot = false,
+ hasParentOverflowHidden = null,
+ hasParentFixedPosition = null,
+ parentNotVisible = false,
+ }
+ ) {
+ let node;
+
+ if (!element) {
+ return;
+ }
+
+ const elementHiddenByOverflow = isElementHiddenByOverflow(element, {
+ hasParentFixedPosition,
+ hasParentOverflowHidden,
+ parentNotVisible,
+ });
+
+ if (isVisible(element) && !isRoot && !elementHiddenByOverflow) {
+ elements.push(element);
+ }
+
+ for (node = element.firstChild; node; node = node.nextSibling) {
+ if (node.nodeType === 1) {
+ walk(node, {
+ isRoot: false,
+ parentNotVisible: elementHiddenByOverflow,
+ hasParentFixedPosition: hasFixedPosition(element)
+ ? element
+ : hasParentFixedPosition,
+ hasParentOverflowHidden: hasOverflow(element)
+ ? element
+ : hasParentOverflowHidden,
+ });
+ }
+ }
+ }
+
+ function getRootElement(rootSelector) {
+ const roots = Array.from(
+ // Replace all > * from the selector
+ // We want the parent and not all the children
+ window.document.querySelectorAll(
+ rootSelector.replace(/(\s+)?>(\s+)?\*/g, '')
+ )
+ );
+
+ if (roots.length === 1) {
+ return roots[0];
+ }
+
+ // Find the deepest node
+ return roots.reduce((root, node) => {
+ if (!root) {
+ return node;
+ }
+
+ if (root.contains(node) && root !== node) {
+ return node;
+ }
+
+ return root;
+ }, null);
+ }
+
+ const root = getRootElement(selector);
+
+ if (!root) {
+ throw new Error('No visible elements found');
+ }
- const elements = Array.from(window.document.querySelectorAll(selector))
- .filter(isVisisble)
- .filter(isNotWrapperElement);
+ walk(root, { isRoot: true });
if (elements.length === 0) {
throw new Error('No visible elements found');
diff --git a/packages/browser/src/get-selector-box-size.spec.js b/packages/browser/src/get-selector-box-size.spec.js
index e77deed7..4dfca400 100644
--- a/packages/browser/src/get-selector-box-size.spec.js
+++ b/packages/browser/src/get-selector-box-size.spec.js
@@ -1,37 +1,49 @@
+/**
+ * @jest-environment jsdom
+ */
+
+/* eslint-env browser */
+
const getSelectorBoxSize = require('./get-selector-box-size');
-const createMockWindow = elements => ({
- document: {
- querySelectorAll: () =>
- elements.map(element =>
- Object.assign({}, element, {
- getBoundingClientRect: () => element,
- contains: () => element.class === 'wrapper',
- })
- ),
- },
- getComputedStyle: element => {
- const { width, height, style = {} } = element;
- return Object.assign({}, style, {
+const addElementsToWrapper = (rects, customMarkup = w => w) => {
+ let wrapper = document.querySelector('#root');
+
+ if (!wrapper) {
+ wrapper = document.createElement('div');
+ wrapper.setAttribute('id', 'root');
+ document.body.appendChild(wrapper);
+ } else {
+ wrapper.innerHTML = '';
+ }
+
+ wrapper = customMarkup(wrapper);
+
+ rects.forEach(({ x, y, width, height, style }) => {
+ const element = document.createElement('div');
+ element.getBoundingClientRect = jest.fn(() => ({
+ x,
+ y,
+ width,
+ height,
+ }));
+ Object.assign(element.style, { padding: '0px', margin: '0px' }, style, {
width: `${width}px`,
height: `${height}px`,
- padding: '0px',
});
- },
-});
+ wrapper.appendChild(element);
+ });
+};
describe('getSelectorBoxSize', () => {
it('should throw an exception when no elements', () => {
- const mockWindow = createMockWindow([]);
- expect(() => getSelectorBoxSize(mockWindow, 'any-selector')).toThrow();
+ expect(() => getSelectorBoxSize(window, 'any-selector')).toThrow();
});
it('should return the box size for a single element', () => {
const mockElementRect = { x: 0, y: 0, width: 10, height: 10 };
- const mockWindow = createMockWindow([mockElementRect]);
- expect(getSelectorBoxSize(mockWindow, 'any-selector')).toEqual(
- mockElementRect
- );
+ addElementsToWrapper([mockElementRect]);
+ expect(getSelectorBoxSize(window, '#root > *')).toEqual(mockElementRect);
});
/**
@@ -66,8 +78,8 @@ describe('getSelectorBoxSize', () => {
{ x: 0, y: 0, width: 30, height: 40 },
{ x: 0, y: 40, width: 30, height: 40 },
];
- const mockWindow = createMockWindow(mockElementRects);
- expect(getSelectorBoxSize(mockWindow, 'any-selector')).toEqual({
+ addElementsToWrapper(mockElementRects);
+ expect(getSelectorBoxSize(window, '#root > *')).toEqual({
x: 0,
y: 0,
width: 30,
@@ -121,8 +133,8 @@ describe('getSelectorBoxSize', () => {
{ x: 40, y: 40, width: 60, height: 60 },
{ x: 30, y: 120, width: 20, height: 20 },
];
- const mockWindow = createMockWindow(mockElementRects);
- expect(getSelectorBoxSize(mockWindow, 'any-selector')).toEqual({
+ addElementsToWrapper(mockElementRects);
+ expect(getSelectorBoxSize(window, '#root > *')).toEqual({
x: 10,
y: 10,
width: 90,
@@ -136,14 +148,32 @@ describe('getSelectorBoxSize', () => {
{ x: 10, y: 30, width: 60, height: 20 },
{ x: 40, y: 40, width: 60, height: 60 },
{ x: 30, y: 120, width: 20, height: 20 },
- { x: 0, y: 0, width: 1000, height: 1000, class: 'wrapper' },
];
- const mockWindow = createMockWindow(mockElementRects);
- expect(getSelectorBoxSize(mockWindow, 'any-selector')).toEqual({
- x: 10,
- y: 10,
- width: 90,
- height: 130,
+ addElementsToWrapper(mockElementRects, root => {
+ const wrapper = document.createElement('div');
+ wrapper.setAttribute('class', 'wrapper');
+ wrapper.getBoundingClientRect = jest.fn(() => ({
+ x: 0,
+ y: 0,
+ width: 1000,
+ height: 1000,
+ }));
+ root.appendChild(wrapper);
+ return wrapper;
+ });
+ expect(getSelectorBoxSize(window, '.wrapper > *, #root > *, body')).toEqual(
+ {
+ x: 10,
+ y: 10,
+ width: 90,
+ height: 130,
+ }
+ );
+ expect(getSelectorBoxSize(window, '#root > *')).toEqual({
+ x: 0,
+ y: 0,
+ width: 1000,
+ height: 1000,
});
});
@@ -156,8 +186,8 @@ describe('getSelectorBoxSize', () => {
{ x: 10, y: 10, width: 0, height: 100 },
{ x: 10, y: 10, width: 100, height: 0 },
];
- const mockWindow = createMockWindow(mockElementRects);
- expect(getSelectorBoxSize(mockWindow, 'any-selector')).toEqual({
+ addElementsToWrapper(mockElementRects);
+ expect(getSelectorBoxSize(window, '#root > *')).toEqual({
x: 0,
y: 0,
width: 10,
diff --git a/packages/browser/src/index.js b/packages/browser/src/index.js
index b89ef624..18a865b9 100644
--- a/packages/browser/src/index.js
+++ b/packages/browser/src/index.js
@@ -2,6 +2,7 @@ const addLokiSessionMarker = require('./add-loki-session-marker');
const awaitLokiReady = require('./await-loki-ready');
const createStorybookConfigurator = require('./configure-storybook');
const disableAnimations = require('./disable-animations');
+const disableInputCaret = require('./disable-input-caret');
const disablePointerEvents = require('./disable-pointer-events');
const getSelectorBoxSize = require('./get-selector-box-size');
const getStories = require('./get-stories');
@@ -11,6 +12,7 @@ module.exports = {
awaitLokiReady,
createStorybookConfigurator,
disableAnimations,
+ disableInputCaret,
disablePointerEvents,
getSelectorBoxSize,
getStories,
diff --git a/packages/renderer-aws-lambda/src/create-aws-lambda-renderer.spec.js b/packages/renderer-aws-lambda/src/create-aws-lambda-renderer.spec.js
index 9b5e6447..2d064d49 100644
--- a/packages/renderer-aws-lambda/src/create-aws-lambda-renderer.spec.js
+++ b/packages/renderer-aws-lambda/src/create-aws-lambda-renderer.spec.js
@@ -65,14 +65,6 @@ const storybook = [
describe('createChromeAWSLambdaRenderer', () => {
describe('.getStorybook', () => {
- it.skip(
- 'fetches stories from webpack dynamic bundles',
- async () => {
- expect(await fetchStorybookFixture('dynamic')).toEqual(storybook);
- },
- DOCKER_TEST_TIMEOUT
- );
-
it(
'fetches stories from static bundles',
async () => {
diff --git a/packages/target-chrome-app/package.json b/packages/target-chrome-app/package.json
index 7fedc19d..942cc0fb 100644
--- a/packages/target-chrome-app/package.json
+++ b/packages/target-chrome-app/package.json
@@ -21,7 +21,7 @@
"main": "src/index.js",
"dependencies": {
"@loki/target-chrome-core": "^0.20.3",
- "chrome-launcher": "^0.12.0",
+ "chrome-launcher": "^0.13.0",
"chrome-remote-interface": "^0.28.0",
"debug": "^4.1.1"
},
diff --git a/packages/target-chrome-core/src/create-chrome-target.js b/packages/target-chrome-core/src/create-chrome-target.js
index 952a4c36..7ab71760 100644
--- a/packages/target-chrome-core/src/create-chrome-target.js
+++ b/packages/target-chrome-core/src/create-chrome-target.js
@@ -1,6 +1,7 @@
const debug = require('debug')('loki:chrome');
const {
disableAnimations,
+ disableInputCaret,
disablePointerEvents,
getSelectorBoxSize,
getStories,
@@ -164,6 +165,7 @@ function createChromeTarget(
await evaluateOnNewDocument(`(${disableAnimations})(window);`);
}
await evaluateOnNewDocument(`(${disablePointerEvents})(window);`);
+ await evaluateOnNewDocument(`(${disableInputCaret})(window);`);
debug(`Navigating to ${url}`);
await Promise.all([Page.navigate({ url }), awaitRequestsFinished()]);
diff --git a/website/siteConfig.js b/website/siteConfig.js
index 164801c3..5a6197b5 100644
--- a/website/siteConfig.js
+++ b/website/siteConfig.js
@@ -15,9 +15,19 @@ const users = [
infoLink: 'https://www.bbc.com',
pinned: true,
},
+ {
+ caption: 'Toptal',
+ image: '/img/users/toptal.svg',
+ infoLink: 'https://www.toptal.com',
+ pinned: true,
+ },
];
const siteConfig = {
+ algolia: {
+ apiKey: '5a53968a2a72331b2f3cbffc75d92819',
+ indexName: 'loki'
+ },
title: 'Loki', // Title for your website.
tagline: 'Visual Regression Testing for Storybook',
url: 'https://loki.js.org', // Your website URL
diff --git a/website/static/img/users/toptal.svg b/website/static/img/users/toptal.svg
new file mode 100644
index 00000000..1c0b7e5c
--- /dev/null
+++ b/website/static/img/users/toptal.svg
@@ -0,0 +1,12 @@
+
+
+
diff --git a/yarn.lock b/yarn.lock
index 1b4033a3..261dee87 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5762,16 +5762,17 @@ chrome-aws-lambda@^2.0.1:
dependencies:
lambdafs "^1.3.0"
-chrome-launcher@^0.12.0:
- version "0.12.0"
- resolved "https://registry.yarnpkg.com/chrome-launcher/-/chrome-launcher-0.12.0.tgz#08db81ef0f7b283c331df2c350e780c38bd0ce3a"
- integrity sha512-rBUP4tvWToiileDi3UR0SbWKoUoDCYTRmVND2sdoBL1xANBgVz8V9h1yQluj3MEQaBJg0fRw7hW82uOPrJus7A==
+chrome-launcher@^0.13.0:
+ version "0.13.2"
+ resolved "https://registry.yarnpkg.com/chrome-launcher/-/chrome-launcher-0.13.2.tgz#d61a94c33d616ff027b346ac20dad50d1209f8f7"
+ integrity sha512-zWD9RVVKd8Nx2xKGY4G08lb3nCD+2hmICxovvRE9QjBKQzHFvCYqGlsw15b4zUxLKq3wXEwVbR/yLtMbfk7JbQ==
dependencies:
"@types/node" "*"
- is-wsl "^2.1.0"
+ escape-string-regexp "^1.0.5"
+ is-wsl "^2.2.0"
lighthouse-logger "^1.0.0"
- mkdirp "0.5.1"
- rimraf "^2.6.1"
+ mkdirp "^0.5.3"
+ rimraf "^3.0.2"
chrome-remote-interface@^0.28.0:
version "0.28.0"
@@ -10601,11 +10602,18 @@ is-wsl@^1.1.0:
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=
-is-wsl@^2.1.0, is-wsl@^2.1.1:
+is-wsl@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d"
integrity sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==
+is-wsl@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
+ integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
+ dependencies:
+ is-docker "^2.0.0"
+
is2@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is2/-/is2-2.0.1.tgz#8ac355644840921ce435d94f05d3a94634d3481a"
@@ -12278,6 +12286,11 @@ minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
+minimist@^1.2.5:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
+ integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
+
minimist@~0.0.1:
version "0.0.10"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
@@ -12380,6 +12393,13 @@ mkdirp@*, mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1:
dependencies:
minimist "0.0.8"
+mkdirp@^0.5.3:
+ version "0.5.5"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
+ integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
+ dependencies:
+ minimist "^1.2.5"
+
modify-values@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022"
@@ -15738,6 +15758,13 @@ rimraf@2.6.3:
dependencies:
glob "^7.1.3"
+rimraf@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
+ integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
+ dependencies:
+ glob "^7.1.3"
+
ripemd160@^2.0.0, ripemd160@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"