items={props.availableCategories || []}
- renderItem={i => ({ id: i.id, el: i.label })}
+ renderItem={renderDropdownItem}
onSelect={handleCategorySelected}
placeholder="Category"
/>
@@ -84,7 +86,7 @@ const SearchForm = (props: Props) => {
items={props.availableGenres || []}
- renderItem={i => ({ id: i.id, el: i.label })}
+ renderItem={renderDropdownItem}
onSelect={handleGenreSelected}
placeholder="Genre"
/>
@@ -113,4 +115,4 @@ const SearchForm = (props: Props) => {
SearchForm.displayName = 'SearchForm';
-export default SearchForm;
+export default React.memo(SearchForm);
diff --git a/src/components/scss/_base.scss b/src/components/scss/_base.scss
index 83ef851..fc544da 100644
--- a/src/components/scss/_base.scss
+++ b/src/components/scss/_base.scss
@@ -1,5 +1,5 @@
@import 'variables';
body {
- font-family: 'Roboto', Arial, Verdana, Tahoma, sans-serif;
+ font-family: $default-font-family;
}
diff --git a/src/components/scss/_variables.scss b/src/components/scss/_variables.scss
index d375a13..303732e 100644
--- a/src/components/scss/_variables.scss
+++ b/src/components/scss/_variables.scss
@@ -52,3 +52,5 @@ $times: (
@return $map;
}
+
+$default-font-family: 'Roboto', Arial, Verdana, Tahoma, sans-serif;
diff --git a/testcafe/compare-imgs.js b/testcafe/compare-imgs.js
index d3e57d5..1b38c1b 100644
--- a/testcafe/compare-imgs.js
+++ b/testcafe/compare-imgs.js
@@ -55,12 +55,9 @@ function getScreenshotsPaths() {
);
}
-async function compareImgs(imgPath1, imgPath2) {
- // eslint-disable-next-line no-console
- console.log(`Comparing images: "${path.basename(imgPath1)}"`);
+async function getMistachedPixels(imgPath1, imgPath2) {
const imgs = await Promise.all([getPngData(imgPath1), getPngData(imgPath2)]);
const diff = new PNG({ width: imgs[0].width, height: imgs[0].height });
-
const mismatchedPixels = pixelmatch(
imgs[0].data,
imgs[1].data,
@@ -71,23 +68,50 @@ async function compareImgs(imgPath1, imgPath2) {
threshold: 0.2
}
);
+ return { mismatchedPixels, diff };
+}
- if (mismatchedPixels === 0) return;
-
- const imgFileName = path.basename(imgPath1);
- const browserDir = path.basename(path.dirname(imgPath1));
-
+function handleMismatchError(imgPath, diff, mismatchedPixels) {
+ const imgFileName = path.basename(imgPath);
+ const browserDir = path.basename(path.dirname(imgPath));
if (!fs.existsSync(path.join(diffsPath, browserDir)))
fs.mkdirSync(path.join(diffsPath, browserDir));
- diff.pack().pipe(fs.createWriteStream(path.join(diffsPath, browserDir, imgFileName)));
- throw new Error(`${mismatchedPixels} mismatched pixels for "${imgFileName}"`);
+ return new Promise(resolve => {
+ const writableStream = fs.createWriteStream(
+ path.join(diffsPath, browserDir, imgFileName)
+ );
+
+ diff.pack().pipe(writableStream);
+
+ writableStream.once('close', () =>
+ resolve({
+ imgFileName,
+ mismatchedPixels
+ })
+ );
+ });
+}
+
+async function compareImgs(imgPath1, imgPath2) {
+ // eslint-disable-next-line no-console
+ console.log(`Comparing images: "${path.basename(imgPath1)}"`);
+ const { mismatchedPixels, diff } = await getMistachedPixels(imgPath1, imgPath2);
+
+ if (mismatchedPixels === 0) return null;
+
+ return handleMismatchError(imgPath1, diff, mismatchedPixels);
}
async function diffScreenshots() {
if (fs.existsSync(diffsPath)) clearDir(diffsPath);
else fs.mkdirSync(diffsPath);
- await Promise.all(getScreenshotsPaths().map(r => compareImgs(...r)));
+ const results = await Promise.all(getScreenshotsPaths().map(r => compareImgs(...r)));
+ if (results.some(r => !!r)) {
+ // eslint-disable-next-line no-console
+ console.table(results);
+ throw new Error('Visual regression errors were found');
+ }
}
module.exports = function compareScreenshots() {
diff --git a/testcafe/pages/home.js b/testcafe/pages/home.js
index 321c1e7..d916f16 100644
--- a/testcafe/pages/home.js
+++ b/testcafe/pages/home.js
@@ -56,4 +56,9 @@ module.exports = class HomePage extends BasePage {
get booksContainer() {
return Selector('.main-layout__books').with({ boundTestRun: this.t });
}
+
+ async takeScreenshotOfBooksContainer() {
+ await this.prepareForScreenshot();
+ await this.t.takeScreenshot();
+ }
};
diff --git a/testcafe/screenshots/base/HeadlessChrome/Feature Home route-Scenario I see book cards-1.png b/testcafe/screenshots/base/HeadlessChrome/Feature Home route-Scenario I see book cards-1.png
index e27fb4f..cdf4c18 100644
Binary files a/testcafe/screenshots/base/HeadlessChrome/Feature Home route-Scenario I see book cards-1.png and b/testcafe/screenshots/base/HeadlessChrome/Feature Home route-Scenario I see book cards-1.png differ
diff --git a/testcafe/steps/home-route.js b/testcafe/steps/home-route.js
index 28a4c00..b563dc0 100644
--- a/testcafe/steps/home-route.js
+++ b/testcafe/steps/home-route.js
@@ -9,8 +9,6 @@ Then(
async (t, [bookCardCount]) => {
const bookCards = await t.ctx.page.getBookCards();
await t.expect(bookCards.length).eql(bookCardCount);
-
- await t.ctx.page.prepareForScreenshot();
- await t.takeElementScreenshot(t.ctx.page.booksContainer);
+ await t.ctx.page.takeScreenshotOfBooksContainer();
}
);
diff --git a/yarn.lock b/yarn.lock
index c9376e1..2a8622c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6414,6 +6414,14 @@ file-loader@^3.0.1:
loader-utils "^1.0.2"
schema-utils "^1.0.0"
+file-loader@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-4.0.0.tgz#c3570783fefb6e1bc0978a856f4bf5825b966c2a"
+ integrity sha512-roAbL6IdSGczwfXxhMi6Zq+jD4IfUpL0jWHD7fvmjdOVb7xBfdRUHe4LpBgO23VtVK5AW1OlWZo0p34Jvx3iWg==
+ dependencies:
+ loader-utils "^1.2.2"
+ schema-utils "^1.0.0"
+
file-system-cache@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/file-system-cache/-/file-system-cache-1.0.5.tgz#84259b36a2bbb8d3d6eb1021d3132ffe64cfff4f"
@@ -9034,7 +9042,7 @@ loader-runner@^2.3.0:
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==
-loader-utils@1.2.3, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.0.3, loader-utils@^1.1.0, loader-utils@^1.2.3:
+loader-utils@1.2.3, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.0.3, loader-utils@^1.1.0, loader-utils@^1.2.2, loader-utils@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==
@@ -11598,10 +11606,10 @@ react-helmet-async@^1.0.2:
react-fast-compare "2.0.4"
shallowequal "1.1.0"
-react-hot-loader@^4.11.1:
- version "4.11.1"
- resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.11.1.tgz#2cabbd0f1c8a44c28837b86d6ce28521e6d9a8ac"
- integrity sha512-HAC0UedYzM3mD+ZaQHesntFO0yi2ftOV4ZMMRTj43E4GvW5sQqYTPvur+6J7EaH3MDr/RqjDKXyCqKepV8+y7w==
+react-hot-loader@^4.12.0:
+ version "4.12.0"
+ resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.12.0.tgz#cb93d78db8b97fd7c1f5ab688a7eeaa4ad47df3f"
+ integrity sha512-N+8ct1euiQnwqqDyX+SrxsgZ13ax4e8JiHbSAPf7xAshPxF3iTqVJQUxOLH90RXOFaT8LLynq0VPz3rCK7AW1A==
dependencies:
fast-levenshtein "^2.0.6"
global "^4.3.0"
@@ -12250,9 +12258,9 @@ replace-ext@1.0.0:
integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=
replicator@^1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/replicator/-/replicator-1.0.3.tgz#c0b3ea31e749015bae5d52273f2ae35d541b87ef"
- integrity sha512-WsKsraaM0x0QHy5CtzdgFXUxyowoBhyNkmPqmZShW6h+rOWnyT6Od3zRdTX9r616rAA6kDC9MKQGnSM/CJKfVQ==
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/replicator/-/replicator-1.0.5.tgz#f1e56df7e276a62afe80c2248b8ac03896f4708f"
+ integrity sha512-saxS4y7NFkLMa92BR4bPHR41GD+f/qoDAwD2xZmN+MpDXgibkxwLO2qk7dCHYtskSkd/bWS8Jy6kC5MZUkg1tw==
request-promise-core@1.1.2:
version "1.1.2"
@@ -14916,7 +14924,7 @@ webpack-sources@^1.1.0, webpack-sources@^1.3.0:
source-list-map "^2.0.0"
source-map "~0.6.1"
-webpack@^4.33.0, webpack@^4.35.0:
+webpack@^4.33.0:
version "4.35.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.35.0.tgz#ad3f0f8190876328806ccb7a36f3ce6e764b8378"
integrity sha512-M5hL3qpVvtr8d4YaJANbAQBc4uT01G33eDpl/psRTBCfjxFTihdhin1NtAKB1ruDwzeVdcsHHV3NX+QsAgOosw==
@@ -14946,6 +14954,36 @@ webpack@^4.33.0, webpack@^4.35.0:
watchpack "^1.5.0"
webpack-sources "^1.3.0"
+webpack@^4.35.2:
+ version "4.35.2"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.35.2.tgz#5c8b8a66602cbbd6ec65c6e6747914a61c1449b1"
+ integrity sha512-TZAmorNymV4q66gAM/h90cEjG+N3627Q2MnkSgKlX/z3DlNVKUtqy57lz1WmZU2+FUZwzM+qm7cGaO95PyrX5A==
+ dependencies:
+ "@webassemblyjs/ast" "1.8.5"
+ "@webassemblyjs/helper-module-context" "1.8.5"
+ "@webassemblyjs/wasm-edit" "1.8.5"
+ "@webassemblyjs/wasm-parser" "1.8.5"
+ acorn "^6.0.5"
+ acorn-dynamic-import "^4.0.0"
+ ajv "^6.1.0"
+ ajv-keywords "^3.1.0"
+ chrome-trace-event "^1.0.0"
+ enhanced-resolve "^4.1.0"
+ eslint-scope "^4.0.0"
+ json-parse-better-errors "^1.0.2"
+ loader-runner "^2.3.0"
+ loader-utils "^1.1.0"
+ memory-fs "~0.4.1"
+ micromatch "^3.1.8"
+ mkdirp "~0.5.0"
+ neo-async "^2.5.0"
+ node-libs-browser "^2.0.0"
+ schema-utils "^1.0.0"
+ tapable "^1.1.0"
+ terser-webpack-plugin "^1.1.0"
+ watchpack "^1.5.0"
+ webpack-sources "^1.3.0"
+
websocket-driver@>=0.5.1:
version "0.7.3"
resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.3.tgz#a2d4e0d4f4f116f1e6297eba58b05d430100e9f9"