├── .eslintignore ├── CHANGELOG.md ├── .travis.yml ├── .editorconfig ├── tsconfig.json ├── .eslintrc.yml ├── LICENSE ├── .gitignore ├── package.json ├── src ├── index.spec.ts └── index.ts ├── README.md └── jest.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | *.d.ts 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.1 (2019-04-09) 2 | 3 | #### Features 4 | 5 | * Initial version ([5be891c](https://github.com/awerlang/observable-profiler/commit/5be891c)) 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | cache: 5 | directories: 6 | - $HOME/.npm 7 | - node_modules 8 | install: 9 | - npm install 10 | script: 11 | - npm run lint 12 | - npm run test:ci 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["es2015", "dom"], 5 | "downlevelIteration": true, 6 | "noImplicitAny": true, 7 | "strict": true, 8 | }, 9 | "exclude": [ 10 | "node_modules", 11 | ], 12 | "include": [ 13 | "src/*.ts", 14 | ], 15 | } 16 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | parser: '@typescript-eslint/parser' 2 | plugins: 3 | - '@typescript-eslint' 4 | parserOptions: 5 | ecmaVersion: 2018 6 | sourceType: module 7 | env: 8 | browser: true 9 | es6: true 10 | globals: 11 | Atomics: readonly 12 | SharedArrayBuffer: readonly 13 | extends: standard 14 | rules: 15 | semi: ['error', 'always'] 16 | comma-dangle: ['error', 'only-multiline'] 17 | indent: ['error', 4] 18 | space-before-function-paren: ['error', 'never'] 19 | 20 | no-unused-vars: off 21 | '@typescript-eslint/no-unused-vars': 22 | - error 23 | 24 | no-useless-constructor: off 25 | '@typescript-eslint/no-useless-constructor': 26 | - error 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 André Werlang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # Dist 64 | dist 65 | *.d.ts 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "observable-profiler", 3 | "version": "0.1.1", 4 | "description": "Tracks new & disposed Observable subscriptions", 5 | "main": "./dist/index.js", 6 | "module": "./dist/_esm5/index.js", 7 | "es2015": "./dist/_esm2015/index.js", 8 | "types": "./index.d.ts", 9 | "files": [ 10 | "index.d.ts", 11 | "dist/**/*" 12 | ], 13 | "dependencies": {}, 14 | "devDependencies": { 15 | "@types/jest": "^24.0.11", 16 | "@typescript-eslint/eslint-plugin": "^1.6.0", 17 | "@typescript-eslint/parser": "^1.6.0", 18 | "eslint": "^5.16.0", 19 | "eslint-config-standard": "^12.0.0", 20 | "eslint-plugin-import": "^2.16.0", 21 | "eslint-plugin-node": "^8.0.1", 22 | "eslint-plugin-promise": "^4.1.1", 23 | "eslint-plugin-standard": "^4.0.0", 24 | "jest": "^24.7.1", 25 | "rxjs": "^6.4.0", 26 | "ts-jest": "^24.0.2", 27 | "typescript": "^3.4.2" 28 | }, 29 | "peerDependencies": { 30 | "rxjs": ">=5.0.0" 31 | }, 32 | "scripts": { 33 | "test": "jest --watch", 34 | "test:ci": "jest", 35 | "lint": "eslint --ext .ts .", 36 | "build:cjs": "tsc -b builds/tsconfig.cjs.json", 37 | "build:esm5": "tsc -b builds/tsconfig.esm5.json", 38 | "build:esm2015": "tsc -b builds/tsconfig.esm2015.json", 39 | "build:typings": "tsc -b builds/tsconfig.typings.json", 40 | "build": "npm run build:cjs && npm run build:esm5 && npm run build:esm2015 && npm run build:typings" 41 | }, 42 | "repository": { 43 | "type": "git", 44 | "url": "git+https://github.com/awerlang/observable-profiler.git" 45 | }, 46 | "keywords": [ 47 | "observable", 48 | "rxjs", 49 | "profiler" 50 | ], 51 | "author": "André Werlang", 52 | "license": "MIT", 53 | "bugs": { 54 | "url": "https://github.com/awerlang/observable-profiler/issues" 55 | }, 56 | "homepage": "https://github.com/awerlang/observable-profiler#readme" 57 | } 58 | -------------------------------------------------------------------------------- /src/index.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | import { Observable, Subject, VirtualTimeScheduler, Subscriber } from 'rxjs'; 3 | import { delay, takeUntil, shareReplay } from 'rxjs/operators'; 4 | 5 | import { setup, track, getSubscribers } from './index'; 6 | 7 | describe('tracks subscriptions', () => { 8 | beforeAll(() => { 9 | setup(Observable); 10 | }); 11 | 12 | beforeEach(() => { 13 | track(); 14 | expect(getSubscribers().current().length).toBe(0); 15 | }); 16 | 17 | afterEach(() => { 18 | const s = getSubscribers().current(); 19 | track(false); 20 | expect(s.length).toBe(0); 21 | }); 22 | 23 | describe('to cold observable', () => { 24 | it('unsubscribes imperatively', () => { 25 | const o = new Observable(); 26 | const s = o.subscribe(); 27 | expect(getSubscribers().current().length).toBe(1); 28 | s.unsubscribe(); 29 | }); 30 | 31 | it('unsubscribes on completion', () => { 32 | let subscriber: Subscriber; 33 | const o = new Observable(s => { subscriber = s; }); 34 | o.subscribe(); 35 | expect(getSubscribers().current().length).toBe(1); 36 | subscriber!.complete(); 37 | }); 38 | 39 | it('unsubscribes with takeUntil', () => { 40 | const end = new Subject(); 41 | const o = new Observable().pipe(takeUntil(end)); 42 | o.subscribe(); 43 | expect(getSubscribers().current().length).toBe(3); 44 | end.next(); 45 | }); 46 | }); 47 | 48 | describe('to hot observable', () => { 49 | it('with delay', () => { 50 | const scheduler = new VirtualTimeScheduler(); 51 | const end = new Subject(); 52 | const o = new Subject().pipe(takeUntil(end), delay(2000, scheduler)); 53 | o.subscribe(); 54 | expect(getSubscribers().current().length).toBe(4); 55 | end.next(); 56 | scheduler.flush(); 57 | }); 58 | 59 | it('with shareReplay', () => { 60 | const o = new Subject().pipe(shareReplay({ bufferSize: 1, refCount: true })); 61 | const s = o.subscribe(); 62 | expect(getSubscribers().current().length).toBe(3); 63 | s.unsubscribe(); 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents the source of an subscription 3 | */ 4 | export class SubscriptionSource extends Error { 5 | /** 6 | * @param subscription A subscription to an Observable 7 | * @param id The id of the subscription chain 8 | */ 9 | constructor(readonly subscription: object, readonly id: number) { 10 | super(); 11 | } 12 | } 13 | 14 | let subscribers: Set; 15 | let isTracking = false; 16 | 17 | /** 18 | * Call setup() once before any calls to track() 19 | * @param Observable Bring your own Observable class to track 20 | */ 21 | export function setup(Observable: any) { 22 | const origSubscribe = Observable.prototype.subscribe; 23 | Observable.prototype.subscribe = subscribe; 24 | 25 | let id = 0; 26 | let root = false; 27 | function subscribe(this: any, ...args: any[]) { 28 | let setRoot = false; 29 | if (!root) { 30 | setRoot = true; 31 | root = true; 32 | id++; 33 | } 34 | const subscription = origSubscribe.apply(this, args); 35 | if (isTracking) { 36 | const currentSubscribers = subscribers; 37 | const sub = new SubscriptionSource(subscription, id); 38 | currentSubscribers.add(sub); 39 | subscription.add(() => { 40 | currentSubscribers.delete(sub); 41 | }); 42 | } 43 | if (setRoot) { 44 | root = false; 45 | } 46 | return subscription; 47 | }; 48 | } 49 | 50 | /** 51 | * Accessor to the current subscription list. 52 | */ 53 | export class Iterator { 54 | constructor(private readonly subscribers: Set) { } 55 | 56 | /** 57 | * Returns a snapshot of current subscriptions 58 | */ 59 | current() { 60 | return [...this.subscribers]; 61 | } 62 | } 63 | 64 | /** 65 | * Returns a snapshot of current subscriptions since tracking started 66 | */ 67 | export function getSubscribers() { 68 | return new Iterator(subscribers); 69 | } 70 | 71 | /** 72 | * Starts/stops tracking of Observable subscriptions 73 | * @param {boolean} track `true` to start; `false` to stop 74 | */ 75 | export function track(track = true) { 76 | if (isTracking === track) { 77 | return; 78 | } 79 | isTracking = track; 80 | if (track) { 81 | subscribers = new Set(); 82 | } 83 | return getSubscribers(); 84 | } 85 | 86 | function delay(ms: number) { 87 | return new Promise(resolve => { 88 | setTimeout(resolve, ms); 89 | }); 90 | } 91 | 92 | /** 93 | * Outputs to console the list of active subscriptions 94 | * @param {string} prefix Prints a prefix on each tracked subscription 95 | * @param {number} timeout Give some leeway (in ms) for time-based subscriptions to finish 96 | * @param {boolean} rewriteStack `true` to remove some noise from stack traces 97 | * @param {RegExp} filterStackRe a custom Regexp object to filter stack frames 98 | * @param {boolean} reportInnerSubscriptions `true` to report indirect subscriptions 99 | * @param {Iterator} subscribers The result of a previous call to `track(false)` 100 | */ 101 | export async function printSubscribers({ 102 | prefix = '', 103 | timeout = 0, 104 | rewriteStack = false, 105 | filterStackRe = undefined, 106 | reportInnerSubscriptions = false, 107 | subscribers = undefined, 108 | }: { 109 | prefix?: string, 110 | timeout?: number, 111 | rewriteStack?: boolean, 112 | filterStackRe?: RegExp, 113 | reportInnerSubscriptions?: boolean, 114 | subscribers?: Iterator, 115 | }) { 116 | const sub = subscribers || getSubscribers(); 117 | 118 | await delay(timeout); 119 | 120 | const current = sub.current(); 121 | if (!current.length) { 122 | return; 123 | } 124 | 125 | console.error(prefix, 'Current subscriptions (including indirect/nested):', current.length); 126 | const map = new Set(); 127 | current.forEach(val => { 128 | if (!reportInnerSubscriptions && map.has(val.id)) { 129 | return; 130 | } 131 | if (rewriteStack || filterStackRe) { 132 | const frames = val.stack!.split('\n'); 133 | const stack = (filterStackRe && frames.filter(it => !it.includes('Observable.subscribe') && filterStackRe!.test(it)).join('\n')) || frames.join('\n'); 134 | val.stack = stack; 135 | } 136 | console.error(prefix, `#${val.id}:`, val); 137 | map.add(val.id); 138 | }); 139 | } 140 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # observable-profiler 2 | 3 | [![npm](https://img.shields.io/npm/v/observable-profiler.svg)](https://www.npmjs.com/package/observable-profiler) 4 | [![GitHub](https://img.shields.io/github/license/awerlang/observable-profiler.svg)](https://opensource.org/licenses/MIT) 5 | [![Travis (.org)](https://img.shields.io/travis/awerlang/observable-profiler.svg)](https://travis-ci.org/awerlang/observable-profiler) 6 | [![Code Climate](https://codeclimate.com/github/awerlang/observable-profiler/badges/gpa.svg)](https://codeclimate.com/github/awerlang/observable-profiler) 7 | 8 | Tracks new & disposed Observable subscriptions. 9 | 10 | ## Usage 11 | 12 | ```ts 13 | import { setup, track, printSubscribers } from 'observable-profiler'; 14 | 15 | // Call `setup` once, passing the Observable class (usually imported from `rxjs`) 16 | setup(Observable); 17 | // We're not tracking subscriptions yet, but have the basic support in-place by monkey-patching `Observable#subscribe()` 18 | 19 | track(); 20 | // Subscriptions at this point are now being tracked 21 | ... 22 | // Call `track(false)` to stop tracking, and return a list of pending subscriptions. 23 | const subscribers = track(false); 24 | // Output to console pending subscriptions (leaks, probably) 25 | printSubscribers({ 26 | subscribers, 27 | }); 28 | ``` 29 | 30 | ## Recipes 31 | 32 | ### Angular Bootstrap 33 | 34 | Begins tracking subscriptions once the app bootstraps (Angular itself doesn't unsubscribe everything, so it's better to start tracking not earlier than this moment). Perform the tests on the app. Call `window.stopProfiler()` once you want a report of pending subscriptions. The app will be unloaded. 35 | 36 | ```ts 37 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 38 | import { Observable } from 'rxjs'; 39 | import { setup, track, printSubscribers } from 'observable-profiler'; 40 | 41 | setup(Observable); 42 | platformBrowserDynamic([]) 43 | .bootstrapModule(AppModule) 44 | .then(ref => { 45 | track(); 46 | (window as any).stopProfiler = () => { 47 | ref.destroy(); 48 | const subscribers = track(false); 49 | printSubscribers({ 50 | subscribers, 51 | }); 52 | } 53 | }); 54 | ``` 55 | 56 | ### Angular Router 57 | 58 | This recipe collects subscriptions during the usage of a page, and reports pending subscriptions once it is navigated away. 59 | Because Angular runs resolvers before the current route is deactivated, it may report false positives (for resolvers). 60 | Also, use this snippet in the root outlet, do not nest trackers. 61 | 62 | ```ts 63 | import { Component } from '@angular/core'; 64 | import { Observable } from 'rxjs'; 65 | import { setup, track, printSubscribers } from 'observable-profiler'; 66 | 67 | import { environment } from '../../../../environments/environment'; 68 | 69 | if (!environment.production) { 70 | setup(Observable); 71 | } 72 | 73 | @Component({ 74 | selector: 'main', 75 | template: '', 76 | }) 77 | export class MainComponent { 78 | onActivate() { 79 | track(); 80 | } 81 | 82 | onDeactivate(component: object) { 83 | const subscribers = track(false); 84 | printSubscribers({ 85 | subscribers, 86 | prefix: component.constructor.name, 87 | timeout: 200, 88 | }); 89 | } 90 | } 91 | ``` 92 | 93 | ## Installation 94 | 95 | npm install observable-profiler 96 | 97 | ## FAQ 98 | 99 | **1. Which recipe should I use?** 100 | 101 | A: If you're using a router, start with the router recipe, subscription tracking begins when the routed component is activated i.e. after its construction. The router recipe is intended to catch leaks as fast and localized as possible. Once you're done with router tests, use the bootstrap recipe, which would catch many more leaks, including work done outside router. 102 | 103 | **2. I'm seeing errors in the browser's console. Are they bugs in the `observable-profiler`?** 104 | 105 | A: The profiler will output a line `Current subscriptions (including indirect/nested): ...`, followed by a list of stack traces. Each stack trace points to a place where a subscription to an observable was made, but not yet released. The subscription might be released at a later point once it goes out of scope, but some of these are actual coding mistakes. 106 | 107 | **3. How can I make sense of the profiler's output?** 108 | 109 | A: The output is a stack trace, see question number 2. The last line in a stack trace likely is pointing to the source of the subscription. If that's on your own code, great, you should be able to fix it by releasing the subscription in the appropriate place and time. If it doesn't point to your own code, then it might be a problem for the third-party to solve. I suggest you report to them as a bug linking to a repro built with this tool. 110 | 111 | **4. There's no output, is that right?** 112 | 113 | A: If you're doing a great job, then that's right, there are no leaks. Or may be the case that no subscription was made during the period the profiler was working, as it can only catch subscriptions after tracking has begun. 114 | 115 | **5. Does the profiler works with libraries built on top of observables, like ngrx?** 116 | 117 | A: Yes. There's nothing special about them. 118 | 119 | **6. Any last advice?** 120 | 121 | A: In components, always subscribe in the `ngOnInit()` lifecycle event. Constructors are only meant for initializing fields in the component. 122 | 123 | ## License 124 | 125 | MIT 126 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // For a detailed explanation regarding each configuration property, visit: 2 | // https://jestjs.io/docs/en/configuration.html 3 | 4 | module.exports = { 5 | // All imported modules in your tests should be mocked automatically 6 | // automock: false, 7 | 8 | // Stop running tests after `n` failures 9 | // bail: 0, 10 | 11 | // Respect "browser" field in package.json when resolving modules 12 | // browser: false, 13 | 14 | // The directory where Jest should store its cached dependency information 15 | // cacheDirectory: "C:\\Users\\Werlang\\AppData\\Local\\Temp\\jest", 16 | 17 | // Automatically clear mock calls and instances between every test 18 | // clearMocks: false, 19 | 20 | // Indicates whether the coverage information should be collected while executing the test 21 | // collectCoverage: false, 22 | 23 | // An array of glob patterns indicating a set of files for which coverage information should be collected 24 | // collectCoverageFrom: null, 25 | 26 | // The directory where Jest should output its coverage files 27 | // coverageDirectory: null, 28 | 29 | // An array of regexp pattern strings used to skip coverage collection 30 | // coveragePathIgnorePatterns: [ 31 | // "\\\\node_modules\\\\" 32 | // ], 33 | 34 | // A list of reporter names that Jest uses when writing coverage reports 35 | // coverageReporters: [ 36 | // "json", 37 | // "text", 38 | // "lcov", 39 | // "clover" 40 | // ], 41 | 42 | // An object that configures minimum threshold enforcement for coverage results 43 | // coverageThreshold: null, 44 | 45 | // A path to a custom dependency extractor 46 | // dependencyExtractor: null, 47 | 48 | // Make calling deprecated APIs throw helpful error messages 49 | // errorOnDeprecated: false, 50 | 51 | // Force coverage collection from ignored files using an array of glob patterns 52 | // forceCoverageMatch: [], 53 | 54 | // A path to a module which exports an async function that is triggered once before all test suites 55 | // globalSetup: null, 56 | 57 | // A path to a module which exports an async function that is triggered once after all test suites 58 | // globalTeardown: null, 59 | 60 | // A set of global variables that need to be available in all test environments 61 | // globals: {}, 62 | 63 | // An array of directory names to be searched recursively up from the requiring module's location 64 | // moduleDirectories: [ 65 | // "node_modules" 66 | // ], 67 | 68 | // An array of file extensions your modules use 69 | // moduleFileExtensions: [ 70 | // "js", 71 | // "json", 72 | // "jsx", 73 | // "ts", 74 | // "tsx", 75 | // "node" 76 | // ], 77 | 78 | // A map from regular expressions to module names that allow to stub out resources with a single module 79 | // moduleNameMapper: {}, 80 | 81 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 82 | // modulePathIgnorePatterns: [], 83 | 84 | // Activates notifications for test results 85 | // notify: false, 86 | 87 | // An enum that specifies notification mode. Requires { notify: true } 88 | // notifyMode: "failure-change", 89 | 90 | // A preset that is used as a base for Jest's configuration 91 | preset: 'ts-jest', 92 | 93 | // Run tests from one or more projects 94 | // projects: null, 95 | 96 | // Use this configuration option to add custom reporters to Jest 97 | // reporters: undefined, 98 | 99 | // Automatically reset mock state between every test 100 | // resetMocks: false, 101 | 102 | // Reset the module registry before running each individual test 103 | // resetModules: false, 104 | 105 | // A path to a custom resolver 106 | // resolver: null, 107 | 108 | // Automatically restore mock state between every test 109 | // restoreMocks: false, 110 | 111 | // The root directory that Jest should scan for tests and modules within 112 | // rootDir: null, 113 | 114 | // A list of paths to directories that Jest should use to search for files in 115 | // roots: [ 116 | // "" 117 | // ], 118 | 119 | // Allows you to use a custom runner instead of Jest's default test runner 120 | // runner: "jest-runner", 121 | 122 | // The paths to modules that run some code to configure or set up the testing environment before each test 123 | // setupFiles: [], 124 | 125 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 126 | // setupFilesAfterEnv: [], 127 | 128 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 129 | // snapshotSerializers: [], 130 | 131 | // The test environment that will be used for testing 132 | testEnvironment: 'node', 133 | 134 | // Options that will be passed to the testEnvironment 135 | // testEnvironmentOptions: {}, 136 | 137 | // Adds a location field to test results 138 | // testLocationInResults: false, 139 | 140 | // The glob patterns Jest uses to detect test files 141 | // testMatch: [ 142 | // "**/__tests__/**/*.[jt]s?(x)", 143 | // "**/?(*.)+(spec|test).[tj]s?(x)" 144 | // ], 145 | 146 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 147 | // testPathIgnorePatterns: [ 148 | // "\\\\node_modules\\\\" 149 | // ], 150 | 151 | // The regexp pattern or array of patterns that Jest uses to detect test files 152 | // testRegex: [], 153 | 154 | // This option allows the use of a custom results processor 155 | // testResultsProcessor: null, 156 | 157 | // This option allows use of a custom test runner 158 | // testRunner: "jasmine2", 159 | 160 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href 161 | // testURL: "http://localhost", 162 | 163 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" 164 | // timers: "real", 165 | 166 | // A map from regular expressions to paths to transformers 167 | // transform: null, 168 | 169 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 170 | // transformIgnorePatterns: [ 171 | // "\\\\node_modules\\\\" 172 | // ], 173 | 174 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 175 | // unmockedModulePathPatterns: undefined, 176 | 177 | // Indicates whether each individual test should be reported during the run 178 | // verbose: null, 179 | 180 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 181 | // watchPathIgnorePatterns: [], 182 | 183 | // Whether to use watchman for file crawling 184 | // watchman: true, 185 | }; 186 | --------------------------------------------------------------------------------