├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .fatherrc.js
├── .github
├── dependabot.yml
└── workflows
│ ├── main.yml
│ ├── test-npm.yml
│ └── test.yml
├── .gitignore
├── .npmignore
├── README.md
├── bin
├── rc-test-migrate.js
└── rc-test.js
├── jest.config.js
├── package.json
├── public
└── modernizr.js
├── src
├── index.ts
├── setup.ts
├── setupAfterEnv.ts
├── setupEnzyme.ts
└── transformers
│ ├── fileTransformer.ts
│ └── jsTransformer.ts
└── tests
├── index.test.tsx
├── setup.ts
└── setupFilesAfterEnv.ts
/.editorconfig:
--------------------------------------------------------------------------------
1 | # top-most EditorConfig file
2 | root = true
3 |
4 | # Unix-style newlines with a newline ending every file
5 | [*.{js,css}]
6 | end_of_line = lf
7 | insert_final_newline = true
8 | indent_style = space
9 | indent_size = 2
10 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | lib/server/js2html.js
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "rules": {
4 | "no-var": [
5 | 0
6 | ],
7 | "arrow-body-style":[0],
8 | "strict": [
9 | 0
10 | ],
11 | "no-console": [
12 | 0
13 | ],
14 | "func-names": [
15 | 0
16 | ],
17 | "space-before-function-paren": [
18 | 0
19 | ],
20 | "no-param-reassign": [
21 | 0
22 | ],
23 | "vars-on-top": [
24 | 0
25 | ]
26 | }
27 | }
--------------------------------------------------------------------------------
/.fatherrc.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'father';
2 |
3 | export default defineConfig({
4 | platform: 'browser',
5 | cjs: { output: 'lib' },
6 | esm: {
7 | output: 'es',
8 | alias: { 'rc-util/lib': 'rc-util/es' },
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "npm" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 | - package-ecosystem: "github-actions" # See documentation for possible values
13 | directory: "/" # Location of package manifests
14 | schedule:
15 | interval: "weekly"
16 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | pull_request:
7 | branches: [master]
8 |
9 | jobs:
10 | setup:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: checkout
14 | uses: actions/checkout@master
15 |
16 | - uses: actions/setup-node@v1
17 | with:
18 | node-version: '12'
19 |
20 | - name: cache package-lock.json
21 | uses: actions/cache@v2
22 | with:
23 | path: package-temp-dir
24 | key: lock-${{ github.sha }}
25 |
26 | - name: create package-lock.json
27 | run: npm i --package-lock-only
28 |
29 | - name: hack for singe file
30 | run: |
31 | if [ ! -d "package-temp-dir" ]; then
32 | mkdir package-temp-dir
33 | fi
34 | cp package-lock.json package-temp-dir
35 |
36 | - name: cache node_modules
37 | id: node_modules_cache_id
38 | uses: actions/cache@v2
39 | with:
40 | path: node_modules
41 | key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
42 |
43 | - name: install
44 | if: steps.node_modules_cache_id.outputs.cache-hit != 'true'
45 | run: npm ci
46 |
47 | test:
48 | runs-on: ubuntu-latest
49 | steps:
50 | - name: checkout
51 | uses: actions/checkout@master
52 |
53 | - name: restore cache from package-lock.json
54 | uses: actions/cache@v2
55 | with:
56 | path: package-temp-dir
57 | key: lock-${{ github.sha }}
58 |
59 | - name: restore cache from node_modules
60 | uses: actions/cache@v2
61 | with:
62 | path: node_modules
63 | key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
64 |
65 | - name: test
66 | run: npm test
67 |
68 | needs: setup
69 |
--------------------------------------------------------------------------------
/.github/workflows/test-npm.yml:
--------------------------------------------------------------------------------
1 | name: react component workflow
2 |
3 | on: [workflow_call]
4 |
5 | jobs:
6 | react-component-workflow:
7 | name: react component workflow
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v4
11 | - run: npm i --legacy-peer-deps
12 | - run: npm run lint
13 | - run: npx tsc --noEmit
14 | - run: npm run compile
15 | - run: npm run test -- --coverage
16 | - uses: codecov/codecov-action@v5
17 | with:
18 | token: ${{ secrets.CODECOV_TOKEN }}
19 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: react component workflow
2 |
3 | on: [workflow_call]
4 |
5 | jobs:
6 | react-component-workflow:
7 | name: react component workflow
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v4
11 | - uses: oven-sh/setup-bun@v2
12 | - run: bun i
13 | - run: bun run lint
14 | - run: bunx tsc --noEmit
15 | - run: bun run compile
16 | - run: bun run test -- --coverage
17 | - uses: codecov/codecov-action@v5
18 | with:
19 | token: ${{ secrets.CODECOV_TOKEN }}
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | *.log
3 | .idea/
4 | .ipr
5 | .iws
6 | *~
7 | ~*
8 | *.diff
9 | *.patch
10 | *.bak
11 | .DS_Store
12 | Thumbs.db
13 | .project
14 | .*proj
15 | .svn/
16 | *.swp
17 | *.swo
18 | *.pyc
19 | *.pyo
20 | .build
21 | node_modules
22 | _site
23 | sea-modules
24 | spm_modules
25 | .cache
26 | dist
27 | tests/*.css
28 | yarn.lock
29 | package-lock.json
30 | lib/
31 | es/
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | bower_components/
2 | *.cfg
3 | node_modules/
4 | nohup.out
5 | *.iml
6 | .idea/
7 | .ipr
8 | .iws
9 | *~
10 | ~*
11 | *.diff
12 | *.log
13 | *.patch
14 | *.bak
15 | .DS_Store
16 | Thumbs.db
17 | .project
18 | .*proj
19 | .svn/
20 | *.swp
21 | out/
22 | .build
23 | node_modules
24 | _site
25 | sea-modules
26 | spm_modules
27 | .cache
28 | dist
29 | tests/*.css
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # rc-test
2 |
3 | test for react component
4 |
5 | ## Usage
6 |
7 | ```
8 | $ rc-test
9 | ```
10 |
--------------------------------------------------------------------------------
/bin/rc-test-migrate.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const path = require("path");
4 | const semver = require("semver");
5 | const fs = require("fs-extra");
6 |
7 | const VER_FATHER = "father";
8 | const VER_PLUGIN = "@rc-component/father-plugin";
9 | const VER_ENZYME_ADAPTER = "enzyme-adapter-react-16";
10 | const RM_DEPS = ["jest", "father-build", "react-test-renderer"];
11 |
12 | const cwd = process.cwd();
13 |
14 | const pkg = require(path.resolve(cwd, "package.json"));
15 |
16 | // ==================================================================
17 | // Upgrade father version if exist
18 | const fatherVer = pkg.devDependencies[VER_FATHER];
19 | if (fatherVer) {
20 | const fatherMajor = semver.major(semver.minVersion(fatherVer));
21 |
22 | if (fatherMajor < 4) {
23 | console.log("升级 father 至 v4...");
24 | pkg.devDependencies[VER_FATHER] = "^4.0.0";
25 | pkg.devDependencies[VER_PLUGIN] = "^1.0.0";
26 |
27 | console.log("覆盖 .fatherrc...");
28 | fs.writeFileSync(
29 | path.resolve(cwd, ".fatherrc.js"),
30 | `
31 | import { defineConfig } from 'father';
32 |
33 | export default defineConfig({
34 | plugins: ['@rc-component/father-plugin'],
35 | });
36 | `.trim(),
37 | "utf-8"
38 | );
39 |
40 | // Clean up father v2 hooks
41 | console.log("清理遗留 pre-commit...");
42 | fs.removeSync(path.resolve(cwd, ".git/hooks/pre-commit"));
43 | }
44 | }
45 |
46 | // Check if exist enzyme
47 | const existEnzyme = pkg.devDependencies["enzyme"];
48 | if (existEnzyme) {
49 | console.log("添加 enzyme adapter 依赖...");
50 | pkg.devDependencies[VER_ENZYME_ADAPTER] = "^1.15.6";
51 | }
52 |
53 | // ==================================================================
54 | // tsconfig.json
55 | const tsConfigPath = path.resolve(cwd, "tsconfig.json");
56 | if (fs.existsSync(tsConfigPath)) {
57 | console.log("更新 tsconfig jsx 配置为 'react'...");
58 | const tsConfig = require(path.resolve(cwd, "tsconfig.json"));
59 |
60 | tsConfig.compilerOptions.jsx = "react";
61 |
62 | fs.writeFileSync(tsConfigPath, JSON.stringify(tsConfig, null, 2));
63 | }
64 |
65 | // ==================================================================
66 | // Update script
67 | pkg.scripts.test = "rc-test";
68 |
69 | // Remove origin test content
70 | RM_DEPS.forEach((dep) => {
71 | delete pkg.devDependencies[dep];
72 | });
73 |
74 | fs.writeFileSync(
75 | path.resolve(cwd, "package.json"),
76 | JSON.stringify(pkg, null, 2),
77 | "utf-8"
78 | );
79 |
80 | // ==================================================================
81 | // Print Tips
82 | console.log("");
83 | console.log("更新完成,请检查以下内容:");
84 | console.log(" - 更新 .github/workflows 中 CI node 版本至 16");
85 | console.log(
86 | " - 移除 jest.config.js 中关于 @testing-library/jsdom 的 setupFilesAfterEnv 配置"
87 | );
88 | console.log(" - 重新安装依赖 node_modules");
89 |
90 | if (pkg.devDependencies["enzyme"]) {
91 | console.log(" - (可选)移除 enzyme 测试,替换为 @testing-library/react");
92 | }
93 |
--------------------------------------------------------------------------------
/bin/rc-test.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const yParser = require("yargs-parser");
4 | const args = yParser(process.argv.slice(2), {
5 | alias: {
6 | watch: ["w"],
7 | version: ["v"],
8 | },
9 | });
10 |
11 | if (args.version) {
12 | console.log(require("../package").version);
13 | process.exit(0);
14 | }
15 |
16 | require("../lib")
17 | .default({
18 | ...args,
19 | })
20 | .catch((e) => {
21 | console.error(e);
22 |
23 | process.exit(1);
24 | });
25 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | setupFiles: ['./tests/setup.ts'],
3 | setupFilesAfterEnv: ['./tests/setupFilesAfterEnv.ts'],
4 | };
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rc-test",
3 | "version": "7.1.2",
4 | "description": "test tools for react component",
5 | "keywords": [
6 | "react",
7 | "test"
8 | ],
9 | "homepage": "http://github.com/react-component/rc-test",
10 | "bugs": {
11 | "url": "http://github.com/react-component/rc-test/issues"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git@github.com:react-component/rc-test.git"
16 | },
17 | "license": "MIT",
18 | "author": {
19 | "name": "yiminghe@gmail.com"
20 | },
21 | "main": "./lib/index",
22 | "module": "./es/index",
23 | "typings": "es/index.d.ts",
24 | "bin": {
25 | "rc-test": "./bin/rc-test.js",
26 | "rc-test-migrate": "./bin/rc-test-migrate.js"
27 | },
28 | "files": [
29 | "lib",
30 | "es"
31 | ],
32 | "scripts": {
33 | "compile": "father build",
34 | "prepublishOnly": "npm run compile && np --no-cleanup --yolo --no-publish",
35 | "test": "npm run compile && npm run test:only",
36 | "test:only": "node ./bin/rc-test.js"
37 | },
38 | "dependencies": {
39 | "@babel/core": "^7.25.2",
40 | "@babel/preset-typescript": "^7.18.6",
41 | "@babel/runtime": "^7.18.0",
42 | "@testing-library/jest-dom": "^5.16.4",
43 | "@testing-library/react": "^13.0.0",
44 | "@umijs/fabric": "^4.0.0",
45 | "babel-jest": "^29.7.0",
46 | "babel-plugin-module-resolver": "^4.1.0",
47 | "babel-preset-umi": "^1.8.4",
48 | "core-js": "^3.25.5",
49 | "fs-extra": "^10.1.0",
50 | "jest": "^29.2.1",
51 | "jest-environment-jsdom": "^29.2.0",
52 | "regenerator-runtime": "^0.13.10",
53 | "umi-utils": "^1.7.3",
54 | "yargs-parser": "^21.1.1"
55 | },
56 | "devDependencies": {
57 | "@types/fs-extra": "^9.0.13",
58 | "@types/jest": "^29.2.0",
59 | "@types/node": "^22.7.5",
60 | "@types/react": "^18.0.0",
61 | "@types/react-dom": "^18.0.0",
62 | "enzyme-adapter-react-16": "^1.15.6",
63 | "eslint": "^7.18.0",
64 | "father": "^4.0.0-rc.8",
65 | "np": "^10.0.7",
66 | "prettier": "^2.1.2",
67 | "react": "^18.0.0",
68 | "react-dom": "^18.0.0",
69 | "typescript": "^5.0.0"
70 | },
71 | "peerDependencies": {
72 | "react": ">=16.9.0",
73 | "react-dom": ">=16.9.0"
74 | },
75 | "engines": {
76 | "node": ">=12.x"
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/public/modernizr.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Modernizr v2.8.3
3 | * www.modernizr.com
4 | *
5 | * Copyright (c) Faruk Ates, Paul Irish, Alex Sexton
6 | * Available under the BSD and MIT licenses: www.modernizr.com/license/
7 | */
8 |
9 | /*
10 | * Modernizr tests which native CSS3 and HTML5 features are available in
11 | * the current UA and makes the results available to you in two ways:
12 | * as properties on a global Modernizr object, and as classes on the
13 | * element. This information allows you to progressively enhance
14 | * your pages with a granular level of control over the experience.
15 | *
16 | * Modernizr has an optional (not included) conditional resource loader
17 | * called Modernizr.load(), based on Yepnope.js (yepnopejs.com).
18 | * To get a build that includes Modernizr.load(), as well as choosing
19 | * which tests to include, go to www.modernizr.com/download/
20 | *
21 | * Authors Faruk Ates, Paul Irish, Alex Sexton
22 | * Contributors Ryan Seddon, Ben Alman
23 | */
24 |
25 | window.Modernizr = (function( window, document, undefined ) {
26 |
27 | var version = '2.8.3',
28 |
29 | Modernizr = {},
30 |
31 | /*>>cssclasses*/
32 | // option for enabling the HTML classes to be added
33 | enableClasses = true,
34 | /*>>cssclasses*/
35 |
36 | docElement = document.documentElement,
37 |
38 | /**
39 | * Create our "modernizr" element that we do most feature tests on.
40 | */
41 | mod = 'modernizr',
42 | modElem = document.createElement(mod),
43 | mStyle = modElem.style,
44 |
45 | /**
46 | * Create the input element for various Web Forms feature tests.
47 | */
48 | inputElem /*>>inputelem*/ = document.createElement('input') /*>>inputelem*/ ,
49 |
50 | /*>>smile*/
51 | smile = ':)',
52 | /*>>smile*/
53 |
54 | toString = {}.toString,
55 |
56 | // TODO :: make the prefixes more granular
57 | /*>>prefixes*/
58 | // List of property values to set for css tests. See ticket #21
59 | prefixes = ' -webkit- -moz- -o- -ms- '.split(' '),
60 | /*>>prefixes*/
61 |
62 | /*>>domprefixes*/
63 | // Following spec is to expose vendor-specific style properties as:
64 | // elem.style.WebkitBorderRadius
65 | // and the following would be incorrect:
66 | // elem.style.webkitBorderRadius
67 |
68 | // Webkit ghosts their properties in lowercase but Opera & Moz do not.
69 | // Microsoft uses a lowercase `ms` instead of the correct `Ms` in IE8+
70 | // erik.eae.net/archives/2008/03/10/21.48.10/
71 |
72 | // More here: github.com/Modernizr/Modernizr/issues/issue/21
73 | omPrefixes = 'Webkit Moz O ms',
74 |
75 | cssomPrefixes = omPrefixes.split(' '),
76 |
77 | domPrefixes = omPrefixes.toLowerCase().split(' '),
78 | /*>>domprefixes*/
79 |
80 | /*>>ns*/
81 | ns = {'svg': 'http://www.w3.org/2000/svg'},
82 | /*>>ns*/
83 |
84 | tests = {},
85 | inputs = {},
86 | attrs = {},
87 |
88 | classes = [],
89 |
90 | slice = classes.slice,
91 |
92 | featureName, // used in testing loop
93 |
94 |
95 | /*>>teststyles*/
96 | // Inject element with style element and some CSS rules
97 | injectElementWithStyles = function( rule, callback, nodes, testnames ) {
98 |
99 | var style, ret, node, docOverflow,
100 | div = document.createElement('div'),
101 | // After page load injecting a fake body doesn't work so check if body exists
102 | body = document.body,
103 | // IE6 and 7 won't return offsetWidth or offsetHeight unless it's in the body element, so we fake it.
104 | fakeBody = body || document.createElement('body');
105 |
106 | if ( parseInt(nodes, 10) ) {
107 | // In order not to give false positives we create a node for each test
108 | // This also allows the method to scale for unspecified uses
109 | while ( nodes-- ) {
110 | node = document.createElement('div');
111 | node.id = testnames ? testnames[nodes] : mod + (nodes + 1);
112 | div.appendChild(node);
113 | }
114 | }
115 |
116 | // '].join('');
122 | div.id = mod;
123 | // IE6 will false positive on some tests due to the style element inside the test div somehow interfering offsetHeight, so insert it into body or fakebody.
124 | // Opera will act all quirky when injecting elements in documentElement when page is served as xml, needs fakebody too. #270
125 | (body ? div : fakeBody).innerHTML += style;
126 | fakeBody.appendChild(div);
127 | if ( !body ) {
128 | //avoid crashing IE8, if background image is used
129 | fakeBody.style.background = '';
130 | //Safari 5.13/5.1.4 OSX stops loading if ::-webkit-scrollbar is used and scrollbars are visible
131 | fakeBody.style.overflow = 'hidden';
132 | docOverflow = docElement.style.overflow;
133 | docElement.style.overflow = 'hidden';
134 | docElement.appendChild(fakeBody);
135 | }
136 |
137 | ret = callback(div, rule);
138 | // If this is done after page load we don't want to remove the body so check if body exists
139 | if ( !body ) {
140 | fakeBody.parentNode.removeChild(fakeBody);
141 | docElement.style.overflow = docOverflow;
142 | } else {
143 | div.parentNode.removeChild(div);
144 | }
145 |
146 | return !!ret;
147 |
148 | },
149 | /*>>teststyles*/
150 |
151 | /*>>mq*/
152 | // adapted from matchMedia polyfill
153 | // by Scott Jehl and Paul Irish
154 | // gist.github.com/786768
155 | testMediaQuery = function( mq ) {
156 |
157 | var matchMedia = window.matchMedia || window.msMatchMedia;
158 | if ( matchMedia ) {
159 | return matchMedia(mq) && matchMedia(mq).matches || false;
160 | }
161 |
162 | var bool;
163 |
164 | injectElementWithStyles('@media ' + mq + ' { #' + mod + ' { position: absolute; } }', function( node ) {
165 | bool = (window.getComputedStyle ?
166 | getComputedStyle(node, null) :
167 | node.currentStyle)['position'] == 'absolute';
168 | });
169 |
170 | return bool;
171 |
172 | },
173 | /*>>mq*/
174 |
175 |
176 | /*>>hasevent*/
177 | //
178 | // isEventSupported determines if a given element supports the given event
179 | // kangax.github.com/iseventsupported/
180 | //
181 | // The following results are known incorrects:
182 | // Modernizr.hasEvent("webkitTransitionEnd", elem) // false negative
183 | // Modernizr.hasEvent("textInput") // in Webkit. github.com/Modernizr/Modernizr/issues/333
184 | // ...
185 | isEventSupported = (function() {
186 |
187 | var TAGNAMES = {
188 | 'select': 'input', 'change': 'input',
189 | 'submit': 'form', 'reset': 'form',
190 | 'error': 'img', 'load': 'img', 'abort': 'img'
191 | };
192 |
193 | function isEventSupported( eventName, element ) {
194 |
195 | element = element || document.createElement(TAGNAMES[eventName] || 'div');
196 | eventName = 'on' + eventName;
197 |
198 | // When using `setAttribute`, IE skips "unload", WebKit skips "unload" and "resize", whereas `in` "catches" those
199 | var isSupported = eventName in element;
200 |
201 | if ( !isSupported ) {
202 | // If it has no `setAttribute` (i.e. doesn't implement Node interface), try generic element
203 | if ( !element.setAttribute ) {
204 | element = document.createElement('div');
205 | }
206 | if ( element.setAttribute && element.removeAttribute ) {
207 | element.setAttribute(eventName, '');
208 | isSupported = is(element[eventName], 'function');
209 |
210 | // If property was created, "remove it" (by setting value to `undefined`)
211 | if ( !is(element[eventName], 'undefined') ) {
212 | element[eventName] = undefined;
213 | }
214 | element.removeAttribute(eventName);
215 | }
216 | }
217 |
218 | element = null;
219 | return isSupported;
220 | }
221 | return isEventSupported;
222 | })(),
223 | /*>>hasevent*/
224 |
225 | // TODO :: Add flag for hasownprop ? didn't last time
226 |
227 | // hasOwnProperty shim by kangax needed for Safari 2.0 support
228 | _hasOwnProperty = ({}).hasOwnProperty, hasOwnProp;
229 |
230 | if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) {
231 | hasOwnProp = function (object, property) {
232 | return _hasOwnProperty.call(object, property);
233 | };
234 | }
235 | else {
236 | hasOwnProp = function (object, property) { /* yes, this can give false positives/negatives, but most of the time we don't care about those */
237 | return ((property in object) && is(object.constructor.prototype[property], 'undefined'));
238 | };
239 | }
240 |
241 | // Adapted from ES5-shim https://github.com/kriskowal/es5-shim/blob/master/es5-shim.js
242 | // es5.github.com/#x15.3.4.5
243 |
244 | if (!Function.prototype.bind) {
245 | Function.prototype.bind = function bind(that) {
246 |
247 | var target = this;
248 |
249 | if (typeof target != "function") {
250 | throw new TypeError();
251 | }
252 |
253 | var args = slice.call(arguments, 1),
254 | bound = function () {
255 |
256 | if (this instanceof bound) {
257 |
258 | var F = function(){};
259 | F.prototype = target.prototype;
260 | var self = new F();
261 |
262 | var result = target.apply(
263 | self,
264 | args.concat(slice.call(arguments))
265 | );
266 | if (Object(result) === result) {
267 | return result;
268 | }
269 | return self;
270 |
271 | } else {
272 |
273 | return target.apply(
274 | that,
275 | args.concat(slice.call(arguments))
276 | );
277 |
278 | }
279 |
280 | };
281 |
282 | return bound;
283 | };
284 | }
285 |
286 | /**
287 | * setCss applies given styles to the Modernizr DOM node.
288 | */
289 | function setCss( str ) {
290 | mStyle.cssText = str;
291 | }
292 |
293 | /**
294 | * setCssAll extrapolates all vendor-specific css strings.
295 | */
296 | function setCssAll( str1, str2 ) {
297 | return setCss(prefixes.join(str1 + ';') + ( str2 || '' ));
298 | }
299 |
300 | /**
301 | * is returns a boolean for if typeof obj is exactly type.
302 | */
303 | function is( obj, type ) {
304 | return typeof obj === type;
305 | }
306 |
307 | /**
308 | * contains returns a boolean for if substr is found within str.
309 | */
310 | function contains( str, substr ) {
311 | return !!~('' + str).indexOf(substr);
312 | }
313 |
314 | /*>>testprop*/
315 |
316 | // testProps is a generic CSS / DOM property test.
317 |
318 | // In testing support for a given CSS property, it's legit to test:
319 | // `elem.style[styleName] !== undefined`
320 | // If the property is supported it will return an empty string,
321 | // if unsupported it will return undefined.
322 |
323 | // We'll take advantage of this quick test and skip setting a style
324 | // on our modernizr element, but instead just testing undefined vs
325 | // empty string.
326 |
327 | // Because the testing of the CSS property names (with "-", as
328 | // opposed to the camelCase DOM properties) is non-portable and
329 | // non-standard but works in WebKit and IE (but not Gecko or Opera),
330 | // we explicitly reject properties with dashes so that authors
331 | // developing in WebKit or IE first don't end up with
332 | // browser-specific content by accident.
333 |
334 | function testProps( props, prefixed ) {
335 | for ( var i in props ) {
336 | var prop = props[i];
337 | if ( !contains(prop, "-") && mStyle[prop] !== undefined ) {
338 | return prefixed == 'pfx' ? prop : true;
339 | }
340 | }
341 | return false;
342 | }
343 | /*>>testprop*/
344 |
345 | // TODO :: add testDOMProps
346 | /**
347 | * testDOMProps is a generic DOM property test; if a browser supports
348 | * a certain property, it won't return undefined for it.
349 | */
350 | function testDOMProps( props, obj, elem ) {
351 | for ( var i in props ) {
352 | var item = obj[props[i]];
353 | if ( item !== undefined) {
354 |
355 | // return the property name as a string
356 | if (elem === false) return props[i];
357 |
358 | // let's bind a function
359 | if (is(item, 'function')){
360 | // default to autobind unless override
361 | return item.bind(elem || obj);
362 | }
363 |
364 | // return the unbound function or obj or value
365 | return item;
366 | }
367 | }
368 | return false;
369 | }
370 |
371 | /*>>testallprops*/
372 | /**
373 | * testPropsAll tests a list of DOM properties we want to check against.
374 | * We specify literally ALL possible (known and/or likely) properties on
375 | * the element including the non-vendor prefixed one, for forward-
376 | * compatibility.
377 | */
378 | function testPropsAll( prop, prefixed, elem ) {
379 |
380 | var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1),
381 | props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' ');
382 |
383 | // did they call .prefixed('boxSizing') or are we just testing a prop?
384 | if(is(prefixed, "string") || is(prefixed, "undefined")) {
385 | return testProps(props, prefixed);
386 |
387 | // otherwise, they called .prefixed('requestAnimationFrame', window[, elem])
388 | } else {
389 | props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' ');
390 | return testDOMProps(props, prefixed, elem);
391 | }
392 | }
393 | /*>>testallprops*/
394 |
395 |
396 | /**
397 | * Tests
398 | * -----
399 | */
400 |
401 | // The *new* flexbox
402 | // dev.w3.org/csswg/css3-flexbox
403 |
404 | tests['flexbox'] = function() {
405 | return testPropsAll('flexWrap');
406 | };
407 |
408 | // The *old* flexbox
409 | // www.w3.org/TR/2009/WD-css3-flexbox-20090723/
410 |
411 | tests['flexboxlegacy'] = function() {
412 | return testPropsAll('boxDirection');
413 | };
414 |
415 | // On the S60 and BB Storm, getContext exists, but always returns undefined
416 | // so we actually have to call getContext() to verify
417 | // github.com/Modernizr/Modernizr/issues/issue/97/
418 |
419 | tests['canvas'] = function() {
420 | var elem = document.createElement('canvas');
421 | return !!(elem.getContext && elem.getContext('2d'));
422 | };
423 |
424 | tests['canvastext'] = function() {
425 | return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function'));
426 | };
427 |
428 | // webk.it/70117 is tracking a legit WebGL feature detect proposal
429 |
430 | // We do a soft detect which may false positive in order to avoid
431 | // an expensive context creation: bugzil.la/732441
432 |
433 | tests['webgl'] = function() {
434 | return !!window.WebGLRenderingContext;
435 | };
436 |
437 | /*
438 | * The Modernizr.touch test only indicates if the browser supports
439 | * touch events, which does not necessarily reflect a touchscreen
440 | * device, as evidenced by tablets running Windows 7 or, alas,
441 | * the Palm Pre / WebOS (touch) phones.
442 | *
443 | * Additionally, Chrome (desktop) used to lie about its support on this,
444 | * but that has since been rectified: crbug.com/36415
445 | *
446 | * We also test for Firefox 4 Multitouch Support.
447 | *
448 | * For more info, see: modernizr.github.com/Modernizr/touch.html
449 | */
450 |
451 | tests['touch'] = function() {
452 | var bool;
453 |
454 | if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
455 | bool = true;
456 | } else {
457 | injectElementWithStyles(['@media (',prefixes.join('touch-enabled),('),mod,')','{#modernizr{top:9px;position:absolute}}'].join(''), function( node ) {
458 | bool = node.offsetTop === 9;
459 | });
460 | }
461 |
462 | return bool;
463 | };
464 |
465 |
466 | // geolocation is often considered a trivial feature detect...
467 | // Turns out, it's quite tricky to get right:
468 | //
469 | // Using !!navigator.geolocation does two things we don't want. It:
470 | // 1. Leaks memory in IE9: github.com/Modernizr/Modernizr/issues/513
471 | // 2. Disables page caching in WebKit: webk.it/43956
472 | //
473 | // Meanwhile, in Firefox < 8, an about:config setting could expose
474 | // a false positive that would throw an exception: bugzil.la/688158
475 |
476 | tests['geolocation'] = function() {
477 | return 'geolocation' in navigator;
478 | };
479 |
480 |
481 | tests['postmessage'] = function() {
482 | return !!window.postMessage;
483 | };
484 |
485 |
486 | // Chrome incognito mode used to throw an exception when using openDatabase
487 | // It doesn't anymore.
488 | tests['websqldatabase'] = function() {
489 | return !!window.openDatabase;
490 | };
491 |
492 | // Vendors had inconsistent prefixing with the experimental Indexed DB:
493 | // - Webkit's implementation is accessible through webkitIndexedDB
494 | // - Firefox shipped moz_indexedDB before FF4b9, but since then has been mozIndexedDB
495 | // For speed, we don't test the legacy (and beta-only) indexedDB
496 | tests['indexedDB'] = function() {
497 | return !!testPropsAll("indexedDB", window);
498 | };
499 |
500 | // documentMode logic from YUI to filter out IE8 Compat Mode
501 | // which false positives.
502 | tests['hashchange'] = function() {
503 | return isEventSupported('hashchange', window) && (document.documentMode === undefined || document.documentMode > 7);
504 | };
505 |
506 | // Per 1.6:
507 | // This used to be Modernizr.historymanagement but the longer
508 | // name has been deprecated in favor of a shorter and property-matching one.
509 | // The old API is still available in 1.6, but as of 2.0 will throw a warning,
510 | // and in the first release thereafter disappear entirely.
511 | tests['history'] = function() {
512 | return !!(window.history && history.pushState);
513 | };
514 |
515 | tests['draganddrop'] = function() {
516 | var div = document.createElement('div');
517 | return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div);
518 | };
519 |
520 | // FF3.6 was EOL'ed on 4/24/12, but the ESR version of FF10
521 | // will be supported until FF19 (2/12/13), at which time, ESR becomes FF17.
522 | // FF10 still uses prefixes, so check for it until then.
523 | // for more ESR info, see: mozilla.org/en-US/firefox/organizations/faq/
524 | tests['websockets'] = function() {
525 | return 'WebSocket' in window || 'MozWebSocket' in window;
526 | };
527 |
528 |
529 | // css-tricks.com/rgba-browser-support/
530 | tests['rgba'] = function() {
531 | // Set an rgba() color and check the returned value
532 |
533 | setCss('background-color:rgba(150,255,150,.5)');
534 |
535 | return contains(mStyle.backgroundColor, 'rgba');
536 | };
537 |
538 | tests['hsla'] = function() {
539 | // Same as rgba(), in fact, browsers re-map hsla() to rgba() internally,
540 | // except IE9 who retains it as hsla
541 |
542 | setCss('background-color:hsla(120,40%,100%,.5)');
543 |
544 | return contains(mStyle.backgroundColor, 'rgba') || contains(mStyle.backgroundColor, 'hsla');
545 | };
546 |
547 | tests['multiplebgs'] = function() {
548 | // Setting multiple images AND a color on the background shorthand property
549 | // and then querying the style.background property value for the number of
550 | // occurrences of "url(" is a reliable method for detecting ACTUAL support for this!
551 |
552 | setCss('background:url(https://),url(https://),red url(https://)');
553 |
554 | // If the UA supports multiple backgrounds, there should be three occurrences
555 | // of the string "url(" in the return value for elemStyle.background
556 |
557 | return (/(url\s*\(.*?){3}/).test(mStyle.background);
558 | };
559 |
560 |
561 |
562 | // this will false positive in Opera Mini
563 | // github.com/Modernizr/Modernizr/issues/396
564 |
565 | tests['backgroundsize'] = function() {
566 | return testPropsAll('backgroundSize');
567 | };
568 |
569 | tests['borderimage'] = function() {
570 | return testPropsAll('borderImage');
571 | };
572 |
573 |
574 | // Super comprehensive table about all the unique implementations of
575 | // border-radius: muddledramblings.com/table-of-css3-border-radius-compliance
576 |
577 | tests['borderradius'] = function() {
578 | return testPropsAll('borderRadius');
579 | };
580 |
581 | // WebOS unfortunately false positives on this test.
582 | tests['boxshadow'] = function() {
583 | return testPropsAll('boxShadow');
584 | };
585 |
586 | // FF3.0 will false positive on this test
587 | tests['textshadow'] = function() {
588 | return document.createElement('div').style.textShadow === '';
589 | };
590 |
591 |
592 | tests['opacity'] = function() {
593 | // Browsers that actually have CSS Opacity implemented have done so
594 | // according to spec, which means their return values are within the
595 | // range of [0.0,1.0] - including the leading zero.
596 |
597 | setCssAll('opacity:.55');
598 |
599 | // The non-literal . in this regex is intentional:
600 | // German Chrome returns this value as 0,55
601 | // github.com/Modernizr/Modernizr/issues/#issue/59/comment/516632
602 | return (/^0.55$/).test(mStyle.opacity);
603 | };
604 |
605 |
606 | // Note, Android < 4 will pass this test, but can only animate
607 | // a single property at a time
608 | // goo.gl/v3V4Gp
609 | tests['cssanimations'] = function() {
610 | return testPropsAll('animationName');
611 | };
612 |
613 |
614 | tests['csscolumns'] = function() {
615 | return testPropsAll('columnCount');
616 | };
617 |
618 |
619 | tests['cssgradients'] = function() {
620 | /**
621 | * For CSS Gradients syntax, please see:
622 | * webkit.org/blog/175/introducing-css-gradients/
623 | * developer.mozilla.org/en/CSS/-moz-linear-gradient
624 | * developer.mozilla.org/en/CSS/-moz-radial-gradient
625 | * dev.w3.org/csswg/css3-images/#gradients-
626 | */
627 |
628 | var str1 = 'background-image:',
629 | str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));',
630 | str3 = 'linear-gradient(left top,#9f9, white);';
631 |
632 | setCss(
633 | // legacy webkit syntax (FIXME: remove when syntax not in use anymore)
634 | (str1 + '-webkit- '.split(' ').join(str2 + str1) +
635 | // standard syntax // trailing 'background-image:'
636 | prefixes.join(str3 + str1)).slice(0, -str1.length)
637 | );
638 |
639 | return contains(mStyle.backgroundImage, 'gradient');
640 | };
641 |
642 |
643 | tests['cssreflections'] = function() {
644 | return testPropsAll('boxReflect');
645 | };
646 |
647 |
648 | tests['csstransforms'] = function() {
649 | return !!testPropsAll('transform');
650 | };
651 |
652 |
653 | tests['csstransforms3d'] = function() {
654 |
655 | var ret = !!testPropsAll('perspective');
656 |
657 | // Webkit's 3D transforms are passed off to the browser's own graphics renderer.
658 | // It works fine in Safari on Leopard and Snow Leopard, but not in Chrome in
659 | // some conditions. As a result, Webkit typically recognizes the syntax but
660 | // will sometimes throw a false positive, thus we must do a more thorough check:
661 | if ( ret && 'webkitPerspective' in docElement.style ) {
662 |
663 | // Webkit allows this media query to succeed only if the feature is enabled.
664 | // `@media (transform-3d),(-webkit-transform-3d){ ... }`
665 | injectElementWithStyles('@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}', function( node, rule ) {
666 | ret = node.offsetLeft === 9 && node.offsetHeight === 3;
667 | });
668 | }
669 | return ret;
670 | };
671 |
672 |
673 | tests['csstransitions'] = function() {
674 | return testPropsAll('transition');
675 | };
676 |
677 |
678 | /*>>fontface*/
679 | // @font-face detection routine by Diego Perini
680 | // javascript.nwbox.com/CSSSupport/
681 |
682 | // false positives:
683 | // WebOS github.com/Modernizr/Modernizr/issues/342
684 | // WP7 github.com/Modernizr/Modernizr/issues/538
685 | tests['fontface'] = function() {
686 | var bool;
687 |
688 | injectElementWithStyles('@font-face {font-family:"font";src:url("https://")}', function( node, rule ) {
689 | var style = document.getElementById('smodernizr'),
690 | sheet = style.sheet || style.styleSheet,
691 | cssText = sheet ? (sheet.cssRules && sheet.cssRules[0] ? sheet.cssRules[0].cssText : sheet.cssText || '') : '';
692 |
693 | bool = /src/i.test(cssText) && cssText.indexOf(rule.split(' ')[0]) === 0;
694 | });
695 |
696 | return bool;
697 | };
698 | /*>>fontface*/
699 |
700 | // CSS generated content detection
701 | tests['generatedcontent'] = function() {
702 | var bool;
703 |
704 | injectElementWithStyles(['#',mod,'{font:0/0 a}#',mod,':after{content:"',smile,'";visibility:hidden;font:3px/1 a}'].join(''), function( node ) {
705 | bool = node.offsetHeight >= 3;
706 | });
707 |
708 | return bool;
709 | };
710 |
711 |
712 |
713 | // These tests evaluate support of the video/audio elements, as well as
714 | // testing what types of content they support.
715 | //
716 | // We're using the Boolean constructor here, so that we can extend the value
717 | // e.g. Modernizr.video // true
718 | // Modernizr.video.ogg // 'probably'
719 | //
720 | // Codec values from : github.com/NielsLeenheer/html5test/blob/9106a8/index.html#L845
721 | // thx to NielsLeenheer and zcorpan
722 |
723 | // Note: in some older browsers, "no" was a return value instead of empty string.
724 | // It was live in FF3.5.0 and 3.5.1, but fixed in 3.5.2
725 | // It was also live in Safari 4.0.0 - 4.0.4, but fixed in 4.0.5
726 |
727 | tests['video'] = function() {
728 | var elem = document.createElement('video'),
729 | bool = false;
730 |
731 | // IE9 Running on Windows Server SKU can cause an exception to be thrown, bug #224
732 | try {
733 | if ( bool = !!elem.canPlayType ) {
734 | bool = new Boolean(bool);
735 | bool.ogg = elem.canPlayType('video/ogg; codecs="theora"') .replace(/^no$/,'');
736 |
737 | // Without QuickTime, this value will be `undefined`. github.com/Modernizr/Modernizr/issues/546
738 | bool.h264 = elem.canPlayType('video/mp4; codecs="avc1.42E01E"') .replace(/^no$/,'');
739 |
740 | bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,'');
741 | }
742 |
743 | } catch(e) { }
744 |
745 | return bool;
746 | };
747 |
748 | tests['audio'] = function() {
749 | var elem = document.createElement('audio'),
750 | bool = false;
751 |
752 | try {
753 | if ( bool = !!elem.canPlayType ) {
754 | bool = new Boolean(bool);
755 | bool.ogg = elem.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,'');
756 | bool.mp3 = elem.canPlayType('audio/mpeg;') .replace(/^no$/,'');
757 |
758 | // Mimetypes accepted:
759 | // developer.mozilla.org/En/Media_formats_supported_by_the_audio_and_video_elements
760 | // bit.ly/iphoneoscodecs
761 | bool.wav = elem.canPlayType('audio/wav; codecs="1"') .replace(/^no$/,'');
762 | bool.m4a = ( elem.canPlayType('audio/x-m4a;') ||
763 | elem.canPlayType('audio/aac;')) .replace(/^no$/,'');
764 | }
765 | } catch(e) { }
766 |
767 | return bool;
768 | };
769 |
770 |
771 | // In FF4, if disabled, window.localStorage should === null.
772 |
773 | // Normally, we could not test that directly and need to do a
774 | // `('localStorage' in window) && ` test first because otherwise Firefox will
775 | // throw bugzil.la/365772 if cookies are disabled
776 |
777 | // Also in iOS5 Private Browsing mode, attempting to use localStorage.setItem
778 | // will throw the exception:
779 | // QUOTA_EXCEEDED_ERRROR DOM Exception 22.
780 | // Peculiarly, getItem and removeItem calls do not throw.
781 |
782 | // Because we are forced to try/catch this, we'll go aggressive.
783 |
784 | // Just FWIW: IE8 Compat mode supports these features completely:
785 | // www.quirksmode.org/dom/html5.html
786 | // But IE8 doesn't support either with local files
787 |
788 | tests['localstorage'] = function() {
789 | try {
790 | localStorage.setItem(mod, mod);
791 | localStorage.removeItem(mod);
792 | return true;
793 | } catch(e) {
794 | return false;
795 | }
796 | };
797 |
798 | tests['sessionstorage'] = function() {
799 | try {
800 | sessionStorage.setItem(mod, mod);
801 | sessionStorage.removeItem(mod);
802 | return true;
803 | } catch(e) {
804 | return false;
805 | }
806 | };
807 |
808 |
809 | tests['webworkers'] = function() {
810 | return !!window.Worker;
811 | };
812 |
813 |
814 | tests['applicationcache'] = function() {
815 | return !!window.applicationCache;
816 | };
817 |
818 |
819 | // Thanks to Erik Dahlstrom
820 | tests['svg'] = function() {
821 | return !!document.createElementNS && !!document.createElementNS(ns.svg, 'svg').createSVGRect;
822 | };
823 |
824 | // specifically for SVG inline in HTML, not within XHTML
825 | // test page: paulirish.com/demo/inline-svg
826 | tests['inlinesvg'] = function() {
827 | var div = document.createElement('div');
828 | div.innerHTML = '';
829 | return (div.firstChild && div.firstChild.namespaceURI) == ns.svg;
830 | };
831 |
832 | // SVG SMIL animation
833 | tests['smil'] = function() {
834 | return !!document.createElementNS && /SVGAnimate/.test(toString.call(document.createElementNS(ns.svg, 'animate')));
835 | };
836 |
837 | // This test is only for clip paths in SVG proper, not clip paths on HTML content
838 | // demo: srufaculty.sru.edu/david.dailey/svg/newstuff/clipPath4.svg
839 |
840 | // However read the comments to dig into applying SVG clippaths to HTML content here:
841 | // github.com/Modernizr/Modernizr/issues/213#issuecomment-1149491
842 | tests['svgclippaths'] = function() {
843 | return !!document.createElementNS && /SVGClipPath/.test(toString.call(document.createElementNS(ns.svg, 'clipPath')));
844 | };
845 |
846 | /*>>webforms*/
847 | // input features and input types go directly onto the ret object, bypassing the tests loop.
848 | // Hold this guy to execute in a moment.
849 | function webforms() {
850 | /*>>input*/
851 | // Run through HTML5's new input attributes to see if the UA understands any.
852 | // We're using f which is the element created early on
853 | // Mike Taylr has created a comprehensive resource for testing these attributes
854 | // when applied to all input types:
855 | // miketaylr.com/code/input-type-attr.html
856 | // spec: www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary
857 |
858 | // Only input placeholder is tested while textarea's placeholder is not.
859 | // Currently Safari 4 and Opera 11 have support only for the input placeholder
860 | // Both tests are available in feature-detects/forms-placeholder.js
861 | Modernizr['input'] = (function( props ) {
862 | for ( var i = 0, len = props.length; i < len; i++ ) {
863 | attrs[ props[i] ] = !!(props[i] in inputElem);
864 | }
865 | if (attrs.list){
866 | // safari false positive's on datalist: webk.it/74252
867 | // see also github.com/Modernizr/Modernizr/issues/146
868 | attrs.list = !!(document.createElement('datalist') && window.HTMLDataListElement);
869 | }
870 | return attrs;
871 | })('autocomplete autofocus list placeholder max min multiple pattern required step'.split(' '));
872 | /*>>input*/
873 |
874 | /*>>inputtypes*/
875 | // Run through HTML5's new input types to see if the UA understands any.
876 | // This is put behind the tests runloop because it doesn't return a
877 | // true/false like all the other tests; instead, it returns an object
878 | // containing each input type with its corresponding true/false value
879 |
880 | // Big thanks to @miketaylr for the html5 forms expertise. miketaylr.com/
881 | Modernizr['inputtypes'] = (function(props) {
882 |
883 | for ( var i = 0, bool, inputElemType, defaultView, len = props.length; i < len; i++ ) {
884 |
885 | inputElem.setAttribute('type', inputElemType = props[i]);
886 | bool = inputElem.type !== 'text';
887 |
888 | // We first check to see if the type we give it sticks..
889 | // If the type does, we feed it a textual value, which shouldn't be valid.
890 | // If the value doesn't stick, we know there's input sanitization which infers a custom UI
891 | if ( bool ) {
892 |
893 | inputElem.value = smile;
894 | inputElem.style.cssText = 'position:absolute;visibility:hidden;';
895 |
896 | if ( /^range$/.test(inputElemType) && inputElem.style.WebkitAppearance !== undefined ) {
897 |
898 | docElement.appendChild(inputElem);
899 | defaultView = document.defaultView;
900 |
901 | // Safari 2-4 allows the smiley as a value, despite making a slider
902 | bool = defaultView.getComputedStyle &&
903 | defaultView.getComputedStyle(inputElem, null).WebkitAppearance !== 'textfield' &&
904 | // Mobile android web browser has false positive, so must
905 | // check the height to see if the widget is actually there.
906 | (inputElem.offsetHeight !== 0);
907 |
908 | docElement.removeChild(inputElem);
909 |
910 | } else if ( /^(search|tel)$/.test(inputElemType) ){
911 | // Spec doesn't define any special parsing or detectable UI
912 | // behaviors so we pass these through as true
913 |
914 | // Interestingly, opera fails the earlier test, so it doesn't
915 | // even make it here.
916 |
917 | } else if ( /^(url|email)$/.test(inputElemType) ) {
918 | // Real url and email support comes with prebaked validation.
919 | bool = inputElem.checkValidity && inputElem.checkValidity() === false;
920 |
921 | } else {
922 | // If the upgraded input compontent rejects the :) text, we got a winner
923 | bool = inputElem.value != smile;
924 | }
925 | }
926 |
927 | inputs[ props[i] ] = !!bool;
928 | }
929 | return inputs;
930 | })('search tel url email datetime date month week time datetime-local number range color'.split(' '));
931 | /*>>inputtypes*/
932 | }
933 | /*>>webforms*/
934 |
935 |
936 | // End of test definitions
937 | // -----------------------
938 |
939 |
940 |
941 | // Run through all tests and detect their support in the current UA.
942 | // todo: hypothetically we could be doing an array of tests and use a basic loop here.
943 | for ( var feature in tests ) {
944 | if ( hasOwnProp(tests, feature) ) {
945 | // run the test, throw the return value into the Modernizr,
946 | // then based on that boolean, define an appropriate className
947 | // and push it into an array of classes we'll join later.
948 | featureName = feature.toLowerCase();
949 | Modernizr[featureName] = tests[feature]();
950 |
951 | classes.push((Modernizr[featureName] ? '' : 'no-') + featureName);
952 | }
953 | }
954 |
955 | /*>>webforms*/
956 | // input tests need to run.
957 | Modernizr.input || webforms();
958 | /*>>webforms*/
959 |
960 |
961 | /**
962 | * addTest allows the user to define their own feature tests
963 | * the result will be added onto the Modernizr object,
964 | * as well as an appropriate className set on the html element
965 | *
966 | * @param feature - String naming the feature
967 | * @param test - Function returning true if feature is supported, false if not
968 | */
969 | Modernizr.addTest = function ( feature, test ) {
970 | if ( typeof feature == 'object' ) {
971 | for ( var key in feature ) {
972 | if ( hasOwnProp( feature, key ) ) {
973 | Modernizr.addTest( key, feature[ key ] );
974 | }
975 | }
976 | } else {
977 |
978 | feature = feature.toLowerCase();
979 |
980 | if ( Modernizr[feature] !== undefined ) {
981 | // we're going to quit if you're trying to overwrite an existing test
982 | // if we were to allow it, we'd do this:
983 | // var re = new RegExp("\\b(no-)?" + feature + "\\b");
984 | // docElement.className = docElement.className.replace( re, '' );
985 | // but, no rly, stuff 'em.
986 | return Modernizr;
987 | }
988 |
989 | test = typeof test == 'function' ? test() : test;
990 |
991 | if (typeof enableClasses !== "undefined" && enableClasses) {
992 | docElement.className += ' ' + (test ? '' : 'no-') + feature;
993 | }
994 | Modernizr[feature] = test;
995 |
996 | }
997 |
998 | return Modernizr; // allow chaining.
999 | };
1000 |
1001 |
1002 | // Reset modElem.cssText to nothing to reduce memory footprint.
1003 | setCss('');
1004 | modElem = inputElem = null;
1005 |
1006 | /*>>shiv*/
1007 | /**
1008 | * @preserve HTML5 Shiv prev3.7.1 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
1009 | */
1010 | ;(function(window, document) {
1011 | /*jshint evil:true */
1012 | /** version */
1013 | var version = '3.7.0';
1014 |
1015 | /** Preset options */
1016 | var options = window.html5 || {};
1017 |
1018 | /** Used to skip problem elements */
1019 | var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i;
1020 |
1021 | /** Not all elements can be cloned in IE **/
1022 | var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i;
1023 |
1024 | /** Detect whether the browser supports default html5 styles */
1025 | var supportsHtml5Styles;
1026 |
1027 | /** Name of the expando, to work with multiple documents or to re-shiv one document */
1028 | var expando = '_html5shiv';
1029 |
1030 | /** The id for the the documents expando */
1031 | var expanID = 0;
1032 |
1033 | /** Cached data for each document */
1034 | var expandoData = {};
1035 |
1036 | /** Detect whether the browser supports unknown elements */
1037 | var supportsUnknownElements;
1038 |
1039 | (function() {
1040 | try {
1041 | var a = document.createElement('a');
1042 | a.innerHTML = '';
1043 | //if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles
1044 | supportsHtml5Styles = ('hidden' in a);
1045 |
1046 | supportsUnknownElements = a.childNodes.length == 1 || (function() {
1047 | // assign a false positive if unable to shiv
1048 | (document.createElement)('a');
1049 | var frag = document.createDocumentFragment();
1050 | return (
1051 | typeof frag.cloneNode == 'undefined' ||
1052 | typeof frag.createDocumentFragment == 'undefined' ||
1053 | typeof frag.createElement == 'undefined'
1054 | );
1055 | }());
1056 | } catch(e) {
1057 | // assign a false positive if detection fails => unable to shiv
1058 | supportsHtml5Styles = true;
1059 | supportsUnknownElements = true;
1060 | }
1061 |
1062 | }());
1063 |
1064 | /*--------------------------------------------------------------------------*/
1065 |
1066 | /**
1067 | * Creates a style sheet with the given CSS text and adds it to the document.
1068 | * @private
1069 | * @param {Document} ownerDocument The document.
1070 | * @param {String} cssText The CSS text.
1071 | * @returns {StyleSheet} The style element.
1072 | */
1073 | function addStyleSheet(ownerDocument, cssText) {
1074 | var p = ownerDocument.createElement('p'),
1075 | parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement;
1076 |
1077 | p.innerHTML = 'x';
1078 | return parent.insertBefore(p.lastChild, parent.firstChild);
1079 | }
1080 |
1081 | /**
1082 | * Returns the value of `html5.elements` as an array.
1083 | * @private
1084 | * @returns {Array} An array of shived element node names.
1085 | */
1086 | function getElements() {
1087 | var elements = html5.elements;
1088 | return typeof elements == 'string' ? elements.split(' ') : elements;
1089 | }
1090 |
1091 | /**
1092 | * Returns the data associated to the given document
1093 | * @private
1094 | * @param {Document} ownerDocument The document.
1095 | * @returns {Object} An object of data.
1096 | */
1097 | function getExpandoData(ownerDocument) {
1098 | var data = expandoData[ownerDocument[expando]];
1099 | if (!data) {
1100 | data = {};
1101 | expanID++;
1102 | ownerDocument[expando] = expanID;
1103 | expandoData[expanID] = data;
1104 | }
1105 | return data;
1106 | }
1107 |
1108 | /**
1109 | * returns a shived element for the given nodeName and document
1110 | * @memberOf html5
1111 | * @param {String} nodeName name of the element
1112 | * @param {Document} ownerDocument The context document.
1113 | * @returns {Object} The shived element.
1114 | */
1115 | function createElement(nodeName, ownerDocument, data){
1116 | if (!ownerDocument) {
1117 | ownerDocument = document;
1118 | }
1119 | if(supportsUnknownElements){
1120 | return ownerDocument.createElement(nodeName);
1121 | }
1122 | if (!data) {
1123 | data = getExpandoData(ownerDocument);
1124 | }
1125 | var node;
1126 |
1127 | if (data.cache[nodeName]) {
1128 | node = data.cache[nodeName].cloneNode();
1129 | } else if (saveClones.test(nodeName)) {
1130 | node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode();
1131 | } else {
1132 | node = data.createElem(nodeName);
1133 | }
1134 |
1135 | // Avoid adding some elements to fragments in IE < 9 because
1136 | // * Attributes like `name` or `type` cannot be set/changed once an element
1137 | // is inserted into a document/fragment
1138 | // * Link elements with `src` attributes that are inaccessible, as with
1139 | // a 403 response, will cause the tab/window to crash
1140 | // * Script elements appended to fragments will execute when their `src`
1141 | // or `text` property is set
1142 | return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node;
1143 | }
1144 |
1145 | /**
1146 | * returns a shived DocumentFragment for the given document
1147 | * @memberOf html5
1148 | * @param {Document} ownerDocument The context document.
1149 | * @returns {Object} The shived DocumentFragment.
1150 | */
1151 | function createDocumentFragment(ownerDocument, data){
1152 | if (!ownerDocument) {
1153 | ownerDocument = document;
1154 | }
1155 | if(supportsUnknownElements){
1156 | return ownerDocument.createDocumentFragment();
1157 | }
1158 | data = data || getExpandoData(ownerDocument);
1159 | var clone = data.frag.cloneNode(),
1160 | i = 0,
1161 | elems = getElements(),
1162 | l = elems.length;
1163 | for(;i>shiv*/
1309 |
1310 | // Assign private properties to the return object with prefix
1311 | Modernizr._version = version;
1312 |
1313 | // expose these for the plugin API. Look in the source for how to join() them against your input
1314 | /*>>prefixes*/
1315 | Modernizr._prefixes = prefixes;
1316 | /*>>prefixes*/
1317 | /*>>domprefixes*/
1318 | Modernizr._domPrefixes = domPrefixes;
1319 | Modernizr._cssomPrefixes = cssomPrefixes;
1320 | /*>>domprefixes*/
1321 |
1322 | /*>>mq*/
1323 | // Modernizr.mq tests a given media query, live against the current state of the window
1324 | // A few important notes:
1325 | // * If a browser does not support media queries at all (eg. oldIE) the mq() will always return false
1326 | // * A max-width or orientation query will be evaluated against the current state, which may change later.
1327 | // * You must specify values. Eg. If you are testing support for the min-width media query use:
1328 | // Modernizr.mq('(min-width:0)')
1329 | // usage:
1330 | // Modernizr.mq('only screen and (max-width:768)')
1331 | Modernizr.mq = testMediaQuery;
1332 | /*>>mq*/
1333 |
1334 | /*>>hasevent*/
1335 | // Modernizr.hasEvent() detects support for a given event, with an optional element to test on
1336 | // Modernizr.hasEvent('gesturestart', elem)
1337 | Modernizr.hasEvent = isEventSupported;
1338 | /*>>hasevent*/
1339 |
1340 | /*>>testprop*/
1341 | // Modernizr.testProp() investigates whether a given style property is recognized
1342 | // Note that the property names must be provided in the camelCase variant.
1343 | // Modernizr.testProp('pointerEvents')
1344 | Modernizr.testProp = function(prop){
1345 | return testProps([prop]);
1346 | };
1347 | /*>>testprop*/
1348 |
1349 | /*>>testallprops*/
1350 | // Modernizr.testAllProps() investigates whether a given style property,
1351 | // or any of its vendor-prefixed variants, is recognized
1352 | // Note that the property names must be provided in the camelCase variant.
1353 | // Modernizr.testAllProps('boxSizing')
1354 | Modernizr.testAllProps = testPropsAll;
1355 | /*>>testallprops*/
1356 |
1357 |
1358 | /*>>teststyles*/
1359 | // Modernizr.testStyles() allows you to add custom styles to the document and test an element afterwards
1360 | // Modernizr.testStyles('#modernizr { position:absolute }', function(elem, rule){ ... })
1361 | Modernizr.testStyles = injectElementWithStyles;
1362 | /*>>teststyles*/
1363 |
1364 |
1365 | /*>>prefixed*/
1366 | // Modernizr.prefixed() returns the prefixed or nonprefixed property name variant of your input
1367 | // Modernizr.prefixed('boxSizing') // 'MozBoxSizing'
1368 |
1369 | // Properties must be passed as dom-style camelcase, rather than `box-sizing` hypentated style.
1370 | // Return values will also be the camelCase variant, if you need to translate that to hypenated style use:
1371 | //
1372 | // str.replace(/([A-Z])/g, function(str,m1){ return '-' + m1.toLowerCase(); }).replace(/^ms-/,'-ms-');
1373 |
1374 | // If you're trying to ascertain which transition end event to bind to, you might do something like...
1375 | //
1376 | // var transEndEventNames = {
1377 | // 'WebkitTransition' : 'webkitTransitionEnd',
1378 | // 'MozTransition' : 'transitionend',
1379 | // 'OTransition' : 'oTransitionEnd',
1380 | // 'msTransition' : 'MSTransitionEnd',
1381 | // 'transition' : 'transitionend'
1382 | // },
1383 | // transEndEventName = transEndEventNames[ Modernizr.prefixed('transition') ];
1384 |
1385 | Modernizr.prefixed = function(prop, obj, elem){
1386 | if(!obj) {
1387 | return testPropsAll(prop, 'pfx');
1388 | } else {
1389 | // Testing DOM property e.g. Modernizr.prefixed('requestAnimationFrame', window) // 'mozRequestAnimationFrame'
1390 | return testPropsAll(prop, obj, elem);
1391 | }
1392 | };
1393 | /*>>prefixed*/
1394 |
1395 |
1396 | /*>>cssclasses*/
1397 | // Remove "no-js" class from element, if it exists:
1398 | docElement.className = docElement.className.replace(/(^|\s)no-js(\s|$)/, '$1$2') +
1399 |
1400 | // Add the new classes to the element.
1401 | (enableClasses ? ' js ' + classes.join(' ') : '');
1402 | /*>>cssclasses*/
1403 |
1404 | return Modernizr;
1405 |
1406 | })(this, this.document);
1407 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs-extra';
2 | import { runCLI } from 'jest';
3 | import path from 'path';
4 |
5 | const ALIAS = {
6 | t: 'testNamePattern',
7 | w: 'maxWorkers',
8 | u: 'updateSnapshot',
9 | };
10 |
11 | export function getConfig() {
12 | const cwd = process.cwd();
13 | const pkg = require(path.resolve(cwd, 'package.json'));
14 |
15 | const setupFiles = [require.resolve('./setup.js')];
16 |
17 | if (pkg.devDependencies['enzyme']) {
18 | if (
19 | !pkg.dependencies['enzyme-adapter-react-16'] &&
20 | !pkg.devDependencies['enzyme-adapter-react-16']
21 | ) {
22 | console.log(
23 | '[rc-test] Legacy "enzyme-adapter-react-16" is not in the deps. Please install in dev deps!',
24 | );
25 | }
26 | setupFiles.push(require.resolve('./setupEnzyme.js'));
27 | }
28 |
29 | const config = {
30 | rootDir: cwd,
31 | testEnvironment: 'jsdom',
32 | setupFiles,
33 | setupFilesAfterEnv: [require.resolve('./setupAfterEnv.js')],
34 | transform: {
35 | '\\.(t|j)sx?$': require.resolve('./transformers/jsTransformer'),
36 | '\\.svg$': require.resolve('./transformers/fileTransformer'),
37 | },
38 | // transformIgnorePatterns: [
39 | // // 加 [^/]*? 是为了兼容 tnpm 的目录结构
40 | // // 比如:_umi-test@1.5.5@umi-test
41 | // `node_modules/(?!([^/]*?umi|[^/]*?umi-test|[^/]*?enzyme-adapter-react-16|${transformInclude.join(
42 | // '|',
43 | // )})/)`,
44 | // ],
45 | testMatch: ['**/?*.(spec|test|e2e).(j|t)s?(x)'],
46 | moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json'],
47 | // moduleNameMapper: {
48 | // '\\.(css|less|sass|scss)$': require.resolve('identity-obj-proxy'),
49 | // '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
50 | // require.resolve('./fileMock.js'),
51 | // ...(moduleNameMapper || {}),
52 | // ...(userModuleNameMapper || {}),
53 | // },
54 | testPathIgnorePatterns: ['/node_modules/'],
55 | // 用于设置 jest worker 启动的个数
56 | };
57 |
58 | return config;
59 | }
60 |
61 | export default function (originOpts: any = {}) {
62 | const opts = { ...originOpts };
63 | const cwd = process.cwd();
64 |
65 | let config = getConfig();
66 |
67 | // Merge `jest.config.js`
68 | const userJestConfigFile = path.resolve(cwd, 'jest.config.js');
69 | if (fs.existsSync(userJestConfigFile)) {
70 | const { setupFiles = [], setupFilesAfterEnv = [], ...restConfig } = require(userJestConfigFile);
71 | config = {
72 | ...config,
73 | ...restConfig,
74 | setupFiles: [...config.setupFiles, ...setupFiles],
75 | setupFilesAfterEnv: [...config.setupFilesAfterEnv, ...setupFilesAfterEnv],
76 | };
77 | }
78 |
79 | // Fill jest alias
80 | Object.keys(ALIAS).forEach((key) => {
81 | if (opts[key]) {
82 | opts[ALIAS[key]] = opts[key];
83 | delete opts[key];
84 | }
85 | });
86 |
87 | return new Promise((resolve, reject) => {
88 | const originEnv = process.env.NODE_ENV;
89 | process.env.NODE_ENV = 'test';
90 |
91 | runCLI(
92 | {
93 | config: JSON.stringify(config),
94 | ...opts,
95 | },
96 | [cwd],
97 | )
98 | .then((result) => {
99 | const results = result.results;
100 |
101 | if (results.success) {
102 | resolve(results);
103 | } else {
104 | reject(new Error('Jest failed'));
105 | }
106 | })
107 | .catch((e) => {
108 | console.log(e);
109 | })
110 | .then(() => {
111 | process.env.NODE_ENV = originEnv;
112 | });
113 | });
114 | }
115 |
--------------------------------------------------------------------------------
/src/setup.ts:
--------------------------------------------------------------------------------
1 | import 'core-js/stable';
2 | import 'regenerator-runtime/runtime';
3 | import * as util from 'util';
4 |
5 | // ref: https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
6 | // ref: https://github.com/jsdom/jsdom/issues/2524
7 | Object.defineProperty(global, 'TextEncoder', {
8 | writable: true,
9 | value: util.TextEncoder,
10 | });
11 |
12 | Object.defineProperty(global, 'TextDecoder', {
13 | writable: true,
14 | value: util.TextDecoder,
15 | });
16 |
17 | /* eslint-disable import/first */
18 | global.requestAnimationFrame =
19 | global.requestAnimationFrame ||
20 | function requestAnimationFrame(callback) {
21 | setTimeout(callback, 0);
22 | };
23 |
24 | global.ResizeObserver = jest.fn(() => {
25 | return {
26 | observe() {},
27 | unobserve() {},
28 | disconnect() {},
29 | };
30 | });
--------------------------------------------------------------------------------
/src/setupAfterEnv.ts:
--------------------------------------------------------------------------------
1 | import '@testing-library/jest-dom';
2 |
--------------------------------------------------------------------------------
/src/setupEnzyme.ts:
--------------------------------------------------------------------------------
1 | const Enzyme = require('enzyme');
2 | const Adapter = require('enzyme-adapter-react-16');
3 |
4 | Enzyme.configure({ adapter: new Adapter() });
5 |
--------------------------------------------------------------------------------
/src/transformers/fileTransformer.ts:
--------------------------------------------------------------------------------
1 | import path from "path";
2 |
3 | // Jest 28 has breaking change of this.
4 | const jestPkg = require('jest/package.json');
5 | const aboveJest28 = parseFloat(jestPkg.version) >= 28;
6 |
7 | export default {
8 | process(src: string, filename: string) {
9 | const assetFilename = JSON.stringify(path.basename(filename));
10 |
11 | if (filename.match(/\.svg$/)) {
12 | const code = `module.exports = {
13 | __esModule: true,
14 | default: ${assetFilename},
15 | ReactComponent: ({ svgRef, ...props }) => ({
16 | $$typeof: Symbol.for('react.element'),
17 | type: 'svg',
18 | ref: svgRef || null,
19 | key: null,
20 | props: Object.assign({}, props, {
21 | children: ${assetFilename}
22 | })
23 | }),
24 | };`;
25 |
26 | return aboveJest28 ? { code } : code;
27 | }
28 |
29 | const rawCode = `module.exports = ${assetFilename};`;
30 | return aboveJest28 ? { code: rawCode } : rawCode;
31 | },
32 | };
33 |
--------------------------------------------------------------------------------
/src/transformers/jsTransformer.ts:
--------------------------------------------------------------------------------
1 | import babelJest from 'babel-jest';
2 | import { dirname } from 'path';
3 | import { compatDirname } from 'umi-utils';
4 |
5 | const cwd = process.cwd();
6 |
7 | module.exports = babelJest.createTransformer({
8 | presets: [
9 | require.resolve('@babel/preset-typescript'),
10 | [
11 | require.resolve('babel-preset-umi'),
12 | {
13 | transformRuntime: false,
14 | },
15 | ],
16 | ],
17 | plugins: [
18 | [
19 | require.resolve('babel-plugin-module-resolver'),
20 | {
21 | alias: {
22 | // Projects don't need to install react, react-dom and enzyme
23 | react: compatDirname(
24 | 'react/package',
25 | cwd,
26 | dirname(require.resolve('react/package.json')),
27 | ),
28 | 'react-dom': compatDirname(
29 | 'react-dom/package',
30 | cwd,
31 | dirname(require.resolve('react-dom/package.json')),
32 | ),
33 | // enzyme: compatDirname(
34 | // 'enzyme/package.json',
35 | // cwd,
36 | // dirname(require.resolve('enzyme/package.json')),
37 | // ),
38 | // 'enzyme-adapter-react-16': compatDirname(
39 | // 'enzyme-adapter-react-16/package.json',
40 | // cwd,
41 | // dirname(require.resolve('enzyme-adapter-react-16/package.json')),
42 | // ),
43 | },
44 | },
45 | 'umi-test',
46 | ],
47 | ],
48 | });
49 |
--------------------------------------------------------------------------------
/tests/index.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 |
4 | describe('basic', () => {
5 | it('React', () => {
6 | const { container } = render();
7 |
8 | expect(container.querySelector('.bamboo')).toBeTruthy();
9 | });
10 |
11 | it('async', async () => {
12 | await Promise.resolve();
13 | expect(1).toBeTruthy();
14 | });
15 |
16 | it('testing lib jsdom', () => {
17 | const { container } = render();
18 | expect(container.querySelector('p')).toHaveAttribute('data-test', 'bamboo');
19 | });
20 |
21 | it('correct env', () => {
22 | expect(process.env.NODE_ENV).toEqual('test');
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/tests/setup.ts:
--------------------------------------------------------------------------------
1 | console.log('set up');
--------------------------------------------------------------------------------
/tests/setupFilesAfterEnv.ts:
--------------------------------------------------------------------------------
1 | console.log('set up after env');
--------------------------------------------------------------------------------