feat: add component tests using react-testing-library and jest (#14331)
This commit is contained in:
parent
bab0fc7ac0
commit
c6e8aad745
8
.babelrc
8
.babelrc
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
"presets": [
|
|
||||||
"module:metro-react-native-babel-preset"
|
|
||||||
],
|
|
||||||
"plugins": [
|
|
||||||
"react-native-reanimated/plugin"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
# Xcode
|
# Xcode
|
||||||
#
|
#
|
||||||
|
/component-spec
|
||||||
result/
|
result/
|
||||||
build/
|
build/
|
||||||
*.pbxuser
|
*.pbxuser
|
||||||
|
|
23
Makefile
23
Makefile
|
@ -307,7 +307,7 @@ test-watch: ##@ Watch tests and re-run no changes to cljs files
|
||||||
|
|
||||||
test: export TARGET := clojure
|
test: export TARGET := clojure
|
||||||
test: ##@test Run tests once in NodeJS
|
test: ##@test Run tests once in NodeJS
|
||||||
# Here we creates the gyp bindings for nodejs
|
# Here we create the gyp bindings for nodejs
|
||||||
yarn install
|
yarn install
|
||||||
yarn shadow-cljs compile mocks && \
|
yarn shadow-cljs compile mocks && \
|
||||||
yarn shadow-cljs compile test && \
|
yarn shadow-cljs compile test && \
|
||||||
|
@ -316,14 +316,27 @@ test: ##@test Run tests once in NodeJS
|
||||||
|
|
||||||
run-visual-test-ios: export TARGET := clojure
|
run-visual-test-ios: export TARGET := clojure
|
||||||
run-visual-test-ios: XCODE_DERIVED_DATA := $(HOME)/Library/Developer/Xcode/DerivedData
|
run-visual-test-ios: XCODE_DERIVED_DATA := $(HOME)/Library/Developer/Xcode/DerivedData
|
||||||
run-visual-test-ios: APPLICATION_NAME := $(shell ls $(XCODE_DERIVED_DATA) | grep -E '\bStatusIm-')
|
run-visual-test-ios: APPLICATION_NAME := StatusIm-brfnruzfrkkycpbndmdoeyrigthc
|
||||||
run-visual-test-ios: export TEST_BINARY_PATH := $(XCODE_DERIVED_DATA)/$(APPLICATION_NAME)/Build/Products/Debug-iphonesimulator/StatusIm.app
|
run-visual-test-ios: export TEST_BINARY_PATH := $(XCODE_DERIVED_DATA)/$(APPLICATION_NAME)/Build/Products/Debug-iphonesimulator/StatusIm.app
|
||||||
run-visual-test-ios: ##@test Run tests once in NodeJS
|
run-visual-test-ios: ##@test Run tests once in NodeJS
|
||||||
yarn install
|
|
||||||
detox build --configuration ios.sim.debug && \
|
|
||||||
detox test --configuration ios.sim.debug
|
detox test --configuration ios.sim.debug
|
||||||
|
|
||||||
#--------------
|
component-test-watch: export TARGET := clojure
|
||||||
|
component-test: export COMPONENT_TEST := true
|
||||||
|
component-test: export BABEL_ENV := test
|
||||||
|
component-test-watch: ##@ Watch tests and re-run no changes to cljs files
|
||||||
|
yarn install
|
||||||
|
nodemon --exec 'yarn shadow-cljs compile component-test && jest --config=test/jest/jest.config.js' -e cljs
|
||||||
|
|
||||||
|
component-test: export TARGET := clojure
|
||||||
|
component-test: export COMPONENT_TEST := true
|
||||||
|
component-test: export BABEL_ENV := test
|
||||||
|
component-test: ##@test Run tests once in NodeJS
|
||||||
|
# Here we create the gyp bindings for nodejs
|
||||||
|
yarn install
|
||||||
|
yarn shadow-cljs compile component-test && \
|
||||||
|
jest --config=test/jest/jest.config.js
|
||||||
|
|
||||||
# Other
|
# Other
|
||||||
#--------------
|
#--------------
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
"presets": [
|
||||||
|
"module:metro-react-native-babel-preset"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"react-native-reanimated/plugin"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"test": {
|
||||||
|
"presets": [
|
||||||
|
'@babel/preset-react',
|
||||||
|
[
|
||||||
|
'@babel/preset-env',
|
||||||
|
{
|
||||||
|
targets: {
|
||||||
|
node: '14',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
package.json
10
package.json
|
@ -74,7 +74,7 @@
|
||||||
"react-native-touch-id": "^4.4.1",
|
"react-native-touch-id": "^4.4.1",
|
||||||
"react-native-webview": "git+https://github.com/status-im/react-native-webview.git#refs/tags/v11.16.0-status",
|
"react-native-webview": "git+https://github.com/status-im/react-native-webview.git#refs/tags/v11.16.0-status",
|
||||||
"react-syntax-highlighter": "^15.5.0",
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
"rn-emoji-keyboard": "git+https://github.com/status-im/rn-emoji-keyboard.git#refs/tags/v0.4.3",
|
"rn-emoji-keyboard": "0.7.0",
|
||||||
"tdigest": "^0.1.1"
|
"tdigest": "^0.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -82,15 +82,21 @@
|
||||||
"@babel/helper-builder-react-jsx": "7.0.0",
|
"@babel/helper-builder-react-jsx": "7.0.0",
|
||||||
"@babel/plugin-transform-block-scoping": "7.0.0",
|
"@babel/plugin-transform-block-scoping": "7.0.0",
|
||||||
"@babel/preset-env": "7.1.0",
|
"@babel/preset-env": "7.1.0",
|
||||||
|
"@babel/preset-react": "^7.18.6",
|
||||||
"@babel/register": "7.0.0",
|
"@babel/register": "7.0.0",
|
||||||
|
"@jest/globals": "^25.1.0",
|
||||||
"@mapbox/node-pre-gyp": "^1.0.9",
|
"@mapbox/node-pre-gyp": "^1.0.9",
|
||||||
|
"@testing-library/jest-native": "^5.0.0",
|
||||||
|
"@testing-library/react-native": "^11.2.0",
|
||||||
"@types/jest": "^28.1.6",
|
"@types/jest": "^28.1.6",
|
||||||
"detox": "^19.9.1",
|
"detox": "^19.9.1",
|
||||||
|
"jest": "^25.1.0",
|
||||||
|
"jest-circus": "^26.0.0",
|
||||||
"jest-image-snapshot": "^5.1.0",
|
"jest-image-snapshot": "^5.1.0",
|
||||||
"jest": "^28.1.3",
|
|
||||||
"nodemon": "^2.0.16",
|
"nodemon": "^2.0.16",
|
||||||
"nyc": "^14.1.1",
|
"nyc": "^14.1.1",
|
||||||
"process": "0.11.10",
|
"process": "0.11.10",
|
||||||
|
"react-test-renderer": "16.13.1",
|
||||||
"rn-snoopy": "git+https://github.com/status-im/rn-snoopy.git#refs/tags/v2.0.2-status",
|
"rn-snoopy": "git+https://github.com/status-im/rn-snoopy.git#refs/tags/v2.0.2-status",
|
||||||
"shadow-cljs": "2.11.16"
|
"shadow-cljs": "2.11.16"
|
||||||
},
|
},
|
||||||
|
|
|
@ -64,15 +64,15 @@
|
||||||
{:closure-defines
|
{:closure-defines
|
||||||
{status-im.utils.config/POKT_TOKEN #shadow/env "POKT_TOKEN"
|
{status-im.utils.config/POKT_TOKEN #shadow/env "POKT_TOKEN"
|
||||||
status-im.utils.config/OPENSEA_API_KEY #shadow/env "OPENSEA_API_KEY"}
|
status-im.utils.config/OPENSEA_API_KEY #shadow/env "OPENSEA_API_KEY"}
|
||||||
:compiler-options {:output-feature-set :es6
|
:compiler-options {:output-feature-set :es6
|
||||||
;;disable for android build as there
|
;;disable for android build as there
|
||||||
;;is an intermittent warning with deftype
|
;;is an intermittent warning with deftype
|
||||||
:warnings-as-errors false
|
:warnings-as-errors false
|
||||||
:infer-externs :auto
|
:infer-externs :auto
|
||||||
:static-fns true
|
:static-fns true
|
||||||
:fn-invoke-direct true
|
:fn-invoke-direct true
|
||||||
:optimizations :advanced
|
:optimizations :advanced
|
||||||
:js-options {:js-provider :closure}}}}
|
:js-options {:js-provider :closure}}}}
|
||||||
;; the tests are ran with node, react-native dependencies are mocked
|
;; the tests are ran with node, react-native dependencies are mocked
|
||||||
;; by using node --require override.js, which uses the node-library
|
;; by using node --require override.js, which uses the node-library
|
||||||
;; produced by the target :mocks below and redefines node require
|
;; produced by the target :mocks below and redefines node require
|
||||||
|
@ -106,4 +106,11 @@
|
||||||
:output-to "target/mocks/mocks.js"
|
:output-to "target/mocks/mocks.js"
|
||||||
:output-dir "target/mocks"
|
:output-dir "target/mocks"
|
||||||
:compiler-options {:optimizations :simple
|
:compiler-options {:optimizations :simple
|
||||||
:source-map false}}}}
|
:source-map false}}
|
||||||
|
:component-test {:target :npm-module
|
||||||
|
:entries [quo2.core-spec]
|
||||||
|
:ns-regexp "-component-spec$"
|
||||||
|
:output-dir "component-spec"
|
||||||
|
:compiler-options {:warnings-as-errors false
|
||||||
|
:static-fns false
|
||||||
|
:infer-externs true}}}}
|
||||||
|
|
|
@ -4,9 +4,14 @@
|
||||||
|
|
||||||
(def icon-path "./resources/images/icons2/")
|
(def icon-path "./resources/images/icons2/")
|
||||||
|
|
||||||
|
(defn combine-path [path el]
|
||||||
|
(if (System/getenv "COMPONENT_TEST")
|
||||||
|
(str "." path el "@2x.png")
|
||||||
|
(str "." path el ".png")))
|
||||||
|
|
||||||
(defn require-icon [size path]
|
(defn require-icon [size path]
|
||||||
(fn [el]
|
(fn [el]
|
||||||
(let [s (str "." path el ".png")
|
(let [s (combine-path path el)
|
||||||
k (-> el
|
k (-> el
|
||||||
(cstr/replace "_" "-")
|
(cstr/replace "_" "-")
|
||||||
(cstr/replace " " "-")
|
(cstr/replace " " "-")
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
(ns quo2.components.selectors.--tests--.selectors-component-spec
|
||||||
|
(:require ["@testing-library/react-native" :as rtl]
|
||||||
|
[quo2.components.selectors.selectors :as selectors]
|
||||||
|
[reagent.core :as reagent]))
|
||||||
|
|
||||||
|
(defn render-toggle
|
||||||
|
([]
|
||||||
|
(rtl/render (reagent/as-element [selectors/toggle {:disabled? (:disabled? false)}]))))
|
||||||
|
|
||||||
|
(js/global.test "default render of toggle component"
|
||||||
|
(fn []
|
||||||
|
(render-toggle)
|
||||||
|
(-> (js/expect (rtl/screen.getByTestId "toggle-component"))
|
||||||
|
(.toBeTruthy))))
|
|
@ -133,7 +133,8 @@
|
||||||
:border-radius 20
|
:border-radius 20
|
||||||
:background-color (get-color @checked? disabled? blurred-background?)})
|
:background-color (get-color @checked? disabled? blurred-background?)})
|
||||||
:accessibility-label (str "toggle-" (if @checked? "on" "off"))
|
:accessibility-label (str "toggle-" (if @checked? "on" "off"))
|
||||||
:accessibility-role :checkbox}
|
:accessibility-role :checkbox
|
||||||
|
:testID "toggle-component"}
|
||||||
[rn/view {:style
|
[rn/view {:style
|
||||||
{:margin-left (if @checked? 12 2)
|
{:margin-left (if @checked? 12 2)
|
||||||
:height 16
|
:height 16
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
(ns quo2.core-spec
|
||||||
|
(:require [quo2.components.selectors.--tests--.selectors-component-spec]))
|
|
@ -5,8 +5,11 @@
|
||||||
;;TODO (14/11/22 flexsurfer) this namespace has been moved to the status-im2 namespace, we keep this only for old (status 1.0) code,
|
;;TODO (14/11/22 flexsurfer) this namespace has been moved to the status-im2 namespace, we keep this only for old (status 1.0) code,
|
||||||
;; can be removed with old code later
|
;; can be removed with old code later
|
||||||
|
|
||||||
|
(def get-keyword
|
||||||
|
(if js/global.__TEST__ str keyword))
|
||||||
|
|
||||||
(def default-device-language
|
(def default-device-language
|
||||||
(keyword (.-language react-native-languages)))
|
(get-keyword (.-language react-native-languages)))
|
||||||
|
|
||||||
(def languages #{:ar :bn :de :el :en :es :es_419 :es_AR :fil :fr :hi :id :in :it :ja :ko :ms :nl :pl :pt :pt_BR :ru :tr :vi :zh :zh_Hant :zh_TW})
|
(def languages #{:ar :bn :de :el :en :es :es_419 :es_AR :fil :fr :hi :id :in :it :ja :ko :ms :nl :pl :pt :pt_BR :ru :tr :vi :zh :zh_Hant :zh_TW})
|
||||||
|
|
||||||
|
@ -16,17 +19,17 @@
|
||||||
|
|
||||||
(defn valid-language [lang]
|
(defn valid-language [lang]
|
||||||
(if (contains? languages lang)
|
(if (contains? languages lang)
|
||||||
(keyword lang)
|
(get-keyword lang)
|
||||||
(let [parts (string/split (name lang) #"[\-\_]")
|
(let [parts (string/split (name lang) #"[\-\_]")
|
||||||
short-lang (keyword (str (first parts) "_" (second parts)))
|
short-lang (get-keyword (str (first parts) "_" (second parts)))
|
||||||
shortest-lang (keyword (first parts))]
|
shortest-lang (get-keyword (first parts))]
|
||||||
(if (and (> (count parts) 2) (contains? languages short-lang))
|
(if (and (> (count parts) 2) (contains? languages short-lang))
|
||||||
short-lang
|
short-lang
|
||||||
(when (contains? languages shortest-lang)
|
(when (contains? languages shortest-lang)
|
||||||
shortest-lang)))))
|
shortest-lang)))))
|
||||||
|
|
||||||
(defn require-translation [lang-key]
|
(defn require-translation [lang-key]
|
||||||
(when-let [lang (valid-language (keyword lang-key))]
|
(when-let [lang (valid-language (get-keyword lang-key))]
|
||||||
(case lang
|
(case lang
|
||||||
:ar (js/require "../translations/ar.json")
|
:ar (js/require "../translations/ar.json")
|
||||||
:bn (js/require "../translations/bn.json")
|
:bn (js/require "../translations/bn.json")
|
||||||
|
@ -63,10 +66,10 @@
|
||||||
(assoc default-device-language
|
(assoc default-device-language
|
||||||
(require-translation (-> (name default-device-language)
|
(require-translation (-> (name default-device-language)
|
||||||
(string/replace "-" "_")
|
(string/replace "-" "_")
|
||||||
keyword)))))
|
get-keyword)))))
|
||||||
|
|
||||||
(defn load-language [lang]
|
(defn load-language [lang]
|
||||||
(when-let [lang-key (valid-language (keyword lang))]
|
(when-let [lang-key (valid-language (get-keyword lang))]
|
||||||
(when-not (contains? @loaded-languages lang-key)
|
(when-not (contains? @loaded-languages lang-key)
|
||||||
(aset (.-translations i18n-js)
|
(aset (.-translations i18n-js)
|
||||||
lang
|
lang
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
module.exports = {
|
||||||
|
"preset": "react-native",
|
||||||
|
"setupFilesAfterEnv": ["@testing-library/jest-native/extend-expect"
|
||||||
|
, "../test/jest/jestSetup.js"
|
||||||
|
],
|
||||||
|
"setupFiles": [
|
||||||
|
],
|
||||||
|
"testPathIgnorePatterns": [
|
||||||
|
],
|
||||||
|
"testTimeout": 60000,
|
||||||
|
"transformIgnorePatterns": [
|
||||||
|
"/node_modules/(?!(@react-native|react-native-image-crop-picker|@react-native-community|react-native-linear-gradient|react-native-background-timer|react-native|rn-emoji-keyboard|react-native-languages|react-native-shake|react-native-reanimated)/).*/"
|
||||||
|
],
|
||||||
|
"globals": {
|
||||||
|
"__TEST__": true
|
||||||
|
},
|
||||||
|
"testEnvironment": "node",
|
||||||
|
rootDir: "../../component-spec",
|
||||||
|
testMatch: [
|
||||||
|
"**/*__tests__*"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
const WebSocket = require('ws');
|
||||||
|
const { NativeModules } = require('react-native');
|
||||||
|
|
||||||
|
require('@react-native-async-storage/async-storage/jest/async-storage-mock');
|
||||||
|
require('react-native-gesture-handler/jestSetup');
|
||||||
|
|
||||||
|
|
||||||
|
jest.mock('react-native-reanimated', () => {
|
||||||
|
const Reanimated = require('react-native-reanimated/mock');
|
||||||
|
// The mock for `call` immediately calls the callback which is incorrect
|
||||||
|
// So we override it with a no-op
|
||||||
|
Reanimated.default.call = () => { };
|
||||||
|
|
||||||
|
return Reanimated;
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('@react-native-async-storage/async-storage', () => mockAsyncStorage);
|
||||||
|
|
||||||
|
jest.mock('react-native-navigation', () => ({
|
||||||
|
getNavigationConstants:
|
||||||
|
() => ({ constants: [] }),
|
||||||
|
Navigation: { constants: async () => { } }
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("react-native-background-timer", () => ({}))
|
||||||
|
|
||||||
|
jest.mock('react-native-languages', () => ({
|
||||||
|
RNLanguages: {
|
||||||
|
language: 'en',
|
||||||
|
languages: ['en'],
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
}));
|
||||||
|
|
||||||
|
NativeModules.ReactLocalization = {
|
||||||
|
language: 'en',
|
||||||
|
locale: 'en'
|
||||||
|
};
|
||||||
|
global.navigator = {
|
||||||
|
userAgent: 'node',
|
||||||
|
}
|
||||||
|
|
||||||
|
global.WebSocket = WebSocket
|
|
@ -1,13 +1,14 @@
|
||||||
{
|
{
|
||||||
"globalSetup": "./global-setup.js",
|
"globalSetup": "./visual-test/global-setup.js",
|
||||||
"globalTeardown": "./global-teardown.js",
|
"globalTeardown": "./visual-test/global-teardown.js",
|
||||||
"setupFilesAfterEnv": ["./setup.js"],
|
"setupFilesAfterEnv": ["./visual-test/setup.js"],
|
||||||
"maxWorkers": 1,
|
"maxWorkers": 1,
|
||||||
"testEnvironment": "./environment",
|
"testEnvironment": "./visual-test/environment",
|
||||||
"testRunner": "jest-circus/runner",
|
"testRunner": "jest-circus/runner",
|
||||||
"testTimeout": 120000,
|
"testTimeout": 120000,
|
||||||
"testRegex": "\\.e2e\\.js$",
|
"testRegex": "\\.e2e\\.js$",
|
||||||
"roots": ["../src/"],
|
"roots": ["./src/"],
|
||||||
"reporters": ["detox/runners/jest/streamlineReporter"],
|
"reporters": ["detox/runners/jest/streamlineReporter"],
|
||||||
"verbose": true
|
"verbose": true,
|
||||||
|
"rootDir": "../"
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ const { configureToMatchImageSnapshot } = require('jest-image-snapshot');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const kebabCase = require('lodash/kebabCase');
|
const kebabCase = require('lodash/kebabCase');
|
||||||
const {expect} = require('expect');
|
const expect = require('expect');
|
||||||
|
|
||||||
|
|
||||||
const toMatchImage = configureToMatchImageSnapshot({
|
const toMatchImage = configureToMatchImageSnapshot({
|
||||||
|
@ -15,8 +15,8 @@ expect.extend({ toMatchImage });
|
||||||
expect.extend({
|
expect.extend({
|
||||||
async toMatchImageSnapshot(screenName) {
|
async toMatchImageSnapshot(screenName) {
|
||||||
const platform = await device.getPlatform();
|
const platform = await device.getPlatform();
|
||||||
const deviceName = await device.name.split(' ').slice(1).join ('') ;
|
const deviceName = await device.name.split(' ').slice(1).join('');
|
||||||
const deviceType = 'iPhone 11 Pro' ;
|
const deviceType = 'iPhone 11 Pro';
|
||||||
|
|
||||||
const SNAPSHOTS_DIR = `__image_snapshots__/${platform}/${deviceType}`;
|
const SNAPSHOTS_DIR = `__image_snapshots__/${platform}/${deviceType}`;
|
||||||
|
|
||||||
|
@ -37,5 +37,5 @@ global.jestExpect = expect
|
||||||
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await device.launchApp();
|
await device.launchApp();
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue