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 = () => ( +
+
+ Position 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 +
+
+ Hidden box +
+
+
+); 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"