├── .github
├── no-response.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE.md
├── LICENSE.md
├── PULL_REQUEST_TEMPLATE.md
├── README.md
├── lib
├── cache-panel-view.js
├── main.js
├── package-panel-view.js
├── timecop-view.js
└── window-panel-view.js
├── menus
└── timecop.cson
├── package.json
├── spec
├── async-spec-helpers.js
└── timecop-spec.js
└── styles
└── timecop.less
/.github/no-response.yml:
--------------------------------------------------------------------------------
1 | # Configuration for probot-no-response - https://github.com/probot/no-response
2 |
3 | # Number of days of inactivity before an issue is closed for lack of response
4 | daysUntilClose: 28
5 |
6 | # Label requiring a response
7 | responseRequiredLabel: more-information-needed
8 |
9 | # Comment to post when closing an issue for lack of response. Set to `false` to disable.
10 | closeComment: >
11 | This issue has been automatically closed because there has been no response
12 | to our request for more information from the original author. With only the
13 | information that is currently in the issue, we don't have enough information
14 | to take action. Please reach out if you have or find the answers we need so
15 | that we can investigate further.
16 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push]
4 |
5 | env:
6 | CI: true
7 |
8 | jobs:
9 | Test:
10 | strategy:
11 | matrix:
12 | os: [ubuntu-latest, macos-latest, windows-latest]
13 | channel: [stable, beta]
14 | runs-on: ${{ matrix.os }}
15 | steps:
16 | - uses: actions/checkout@v1
17 | - uses: UziTech/action-setup-atom@v2
18 | with:
19 | version: ${{ matrix.channel }}
20 | - name: Install dependencies
21 | run: apm install
22 | - name: Run tests
23 | run: atom --test spec
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | See the [Atom contributing guide](https://github.com/atom/atom/blob/master/CONTRIBUTING.md)
2 |
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
8 |
9 | ### Prerequisites
10 |
11 | * [ ] Put an X between the brackets on this line if you have done all of the following:
12 | * Reproduced the problem in Safe Mode: http://flight-manual.atom.io/hacking-atom/sections/debugging/#using-safe-mode
13 | * Followed all applicable steps in the debugging guide: http://flight-manual.atom.io/hacking-atom/sections/debugging/
14 | * Checked the FAQs on the message board for common solutions: https://discuss.atom.io/c/faq
15 | * Checked that your issue isn't already filed: https://github.com/issues?utf8=✓&q=is%3Aissue+user%3Aatom
16 | * Checked that there is not already an Atom package that provides the described functionality: https://atom.io/packages
17 |
18 | ### Description
19 |
20 | [Description of the issue]
21 |
22 | ### Steps to Reproduce
23 |
24 | 1. [First Step]
25 | 2. [Second Step]
26 | 3. [and so on...]
27 |
28 | **Expected behavior:** [What you expect to happen]
29 |
30 | **Actual behavior:** [What actually happens]
31 |
32 | **Reproduces how often:** [What percentage of the time does it reproduce?]
33 |
34 | ### Versions
35 |
36 | You can get this information from copy and pasting the output of `atom --version` and `apm --version` from the command line. Also, please include the OS and what version of the OS you're running.
37 |
38 | ### Additional Information
39 |
40 | Any additional information, configuration or data that might be necessary to reproduce the issue.
41 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 GitHub Inc.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Requirements
2 |
3 | * Filling out the template is required. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion.
4 | * All new code requires tests to ensure against regressions
5 |
6 | ### Description of the Change
7 |
8 |
13 |
14 | ### Alternate Designs
15 |
16 |
17 |
18 | ### Benefits
19 |
20 |
21 |
22 | ### Possible Drawbacks
23 |
24 |
25 |
26 | ### Applicable Issues
27 |
28 |
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ##### Atom and all repositories under Atom will be archived on December 15, 2022. Learn more in our [official announcement](https://github.blog/2022-06-08-sunsetting-atom/)
2 | # Timecop package
3 | [](https://travis-ci.org/atom/timecop) [](https://ci.appveyor.com/project/Atom/timecop/branch/master) [](https://david-dm.org/atom/timecop)
4 |
5 | Displays information about where time is spent while Atom loads.
6 |
7 | * Startup time
8 | * Compile cache
9 | * Package loading time
10 | * Package activation time
11 | * Theme loading time
12 | * Theme activation time
13 |
14 | 
15 |
16 | Inspired by [Timecop](http://www.imdb.com/title/tt0111438/) the movie. :watch: :rotating_light:
17 |
--------------------------------------------------------------------------------
/lib/cache-panel-view.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 | /** @jsx etch.dom */
3 |
4 | import path from 'path'
5 | import etch from 'etch'
6 |
7 | export default class CachePanelView {
8 | constructor () {
9 | etch.initialize(this)
10 | }
11 |
12 | update () {}
13 |
14 | destroy () {
15 | return etch.destroy(this)
16 | }
17 |
18 | render () {
19 | return (
20 |
21 |
22 |
Compile Cache
23 |
24 |
25 | CoffeeScript files compiled
26 | Loading…
27 |
28 |
29 |
30 | Babel files compiled
31 | Loading…
32 |
33 |
34 |
35 | Typescript files compiled
36 | Loading…
37 |
38 |
39 |
40 | CSON files compiled
41 | Loading…
42 |
43 |
44 |
45 | Less files compiled
46 | Loading…
47 |
48 |
49 |
50 |
51 | )
52 | }
53 |
54 | populate () {
55 | const compileCacheStats = this.getCompileCacheStats()
56 | if (compileCacheStats) {
57 | this.refs.coffeeCompileCount.classList.add('highlight-info')
58 | this.refs.coffeeCompileCount.textContent = compileCacheStats['.coffee'].misses
59 | this.refs.babelCompileCount.classList.add('highlight-info')
60 | this.refs.babelCompileCount.textContent = compileCacheStats['.js'].misses
61 | this.refs.typescriptCompileCount.classList.add('highlight-info')
62 | this.refs.typescriptCompileCount.textContent = compileCacheStats['.ts'].misses
63 | }
64 |
65 | this.refs.csonCompileCount.classList.add('highlight-info')
66 | this.refs.csonCompileCount.textContent = this.getCsonCompiles()
67 | this.refs.lessCompileCount.classList.add('highlight-info')
68 | this.refs.lessCompileCount.textContent = this.getLessCompiles()
69 | }
70 |
71 | getCompileCacheStats () {
72 | try {
73 | return require(path.join(atom.getLoadSettings().resourcePath, 'src', 'compile-cache')).getCacheStats()
74 | } catch (error) {
75 | return null
76 | }
77 | }
78 |
79 | getCsonCompiles () {
80 | try {
81 | const CSON = require(path.join(atom.getLoadSettings().resourcePath, 'node_modules', 'season'))
82 | if (CSON.getCacheMisses) {
83 | return CSON.getCacheMisses() || 0
84 | } else {
85 | return 0
86 | }
87 | } catch (error) {
88 | return 0
89 | }
90 | }
91 |
92 | getLessCompiles () {
93 | const lessCache = atom.themes.lessCache
94 | if (lessCache && lessCache.cache && lessCache.cache.stats && lessCache.cache.stats.misses) {
95 | return lessCache.cache.stats.misses || 0
96 | } else {
97 | return 0
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/lib/main.js:
--------------------------------------------------------------------------------
1 | const {CompositeDisposable} = require('atom')
2 |
3 | let TimecopView = null
4 | const ViewURI = 'atom://timecop'
5 |
6 | module.exports = {
7 | activate () {
8 | this.subscriptions = new CompositeDisposable()
9 | this.subscriptions.add(atom.workspace.addOpener(filePath => {
10 | if (filePath === ViewURI) return this.createTimecopView({uri: ViewURI})
11 | }))
12 |
13 | this.subscriptions.add(atom.commands.add('atom-workspace', 'timecop:view', () => atom.workspace.open(ViewURI)))
14 | },
15 |
16 | deactivate () {
17 | this.subscriptions.dispose()
18 | },
19 |
20 | createTimecopView (state) {
21 | if (TimecopView == null) TimecopView = require('./timecop-view')
22 | return new TimecopView(state)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lib/package-panel-view.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 | /** @jsx etch.dom */
3 |
4 | import {Disposable} from 'atom'
5 | import etch from 'etch'
6 |
7 | export default class PackagePanelView {
8 | constructor ({title}) {
9 | this.title = title
10 | etch.initialize(this)
11 |
12 | const clickHandler = (event) => {
13 | const target = event.target.closest('a.package')
14 | if (target) {
15 | atom.workspace.open(`atom://config/packages/${target.dataset.package}`)
16 | }
17 | }
18 | this.element.addEventListener('click', clickHandler)
19 | this.disposable = new Disposable(() => { this.element.removeEventListener('click', clickHandler) })
20 | }
21 |
22 | update () {}
23 |
24 | destroy () {
25 | this.disposable.dispose()
26 | return etch.destroy(this)
27 | }
28 |
29 | render () {
30 | return (
31 |
32 |
33 |
{this.title}
34 |
38 |
39 |
40 | )
41 | }
42 |
43 | addPackages (packages, timeKey) {
44 | for (const pack of packages) {
45 | this.addPackage(pack, timeKey)
46 | }
47 | }
48 |
49 | addPackage (pack, timeKey) {
50 | const li = document.createElement('div')
51 | li.classList.add('list-item')
52 |
53 | const a = document.createElement('a')
54 | a.classList.add('inline-block', 'package')
55 | a.dataset.package = pack.name
56 | a.textContent = pack.name
57 | li.appendChild(a)
58 |
59 | const line = document.createElement('span')
60 | line.classList.add('timecop-line')
61 | li.appendChild(line)
62 |
63 | const timeSpan = document.createElement('span')
64 | timeSpan.classList.add('inline-block', pack[timeKey] > 25 ? 'highlight-error' : 'highlight-warning')
65 | timeSpan.textContent = `${pack[timeKey]}ms`
66 | li.appendChild(timeSpan)
67 |
68 | this.refs.list.appendChild(li)
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/lib/timecop-view.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 | /** @jsx etch.dom */
3 |
4 | import _ from 'underscore-plus'
5 | import dedent from 'dedent'
6 | import etch from 'etch'
7 | import CachePanelView from './cache-panel-view'
8 | import PackagePanelView from './package-panel-view'
9 | import WindowPanelView from './window-panel-view'
10 |
11 | export default class TimecopView {
12 | constructor ({uri}) {
13 | this.uri = uri
14 | etch.initialize(this)
15 | this.refs.cacheLoadingPanel.populate()
16 | if (atom.packages.hasLoadedInitialPackages()) {
17 | this.populateLoadingViews()
18 | } else {
19 | atom.packages.onDidLoadInitialPackages(() => this.populateLoadingViews())
20 | }
21 |
22 | if (atom.packages.hasActivatedInitialPackages()) {
23 | this.populateActivationViews()
24 | } else {
25 | atom.packages.onDidActivateInitialPackages(() => this.populateActivationViews())
26 | }
27 | }
28 |
29 | update () {}
30 |
31 | destroy () {
32 | return etch.destroy(this)
33 | }
34 |
35 | render () {
36 | return (
37 |
38 |
39 |
40 |
41 |
42 |
43 |
49 |
50 |
51 | )
52 | }
53 |
54 | populateLoadingViews () {
55 | this.showLoadedPackages()
56 | this.showLoadedThemes()
57 | }
58 |
59 | populateActivationViews () {
60 | this.refs.windowLoadingPanel.populate()
61 | this.showActivePackages()
62 | this.showActiveThemes()
63 | }
64 |
65 | showLoadedPackages () {
66 | const {time, count, packages} = this.getSlowPackages(
67 | atom.packages.getLoadedPackages().filter(pack => pack.getType() !== 'theme'),
68 | 'loadTime'
69 | )
70 | this.refs.packageLoadingPanel.addPackages(packages, 'loadTime')
71 | this.refs.packageLoadingPanel.refs.summary.textContent = dedent`
72 | Loaded ${count} packages in ${time}ms.
73 | ${_.pluralize(packages.length, 'package')} took longer than 5ms to load.
74 | `
75 | }
76 |
77 | showActivePackages () {
78 | const {time, count, packages} = this.getSlowPackages(
79 | atom.packages.getActivePackages().filter(pack => pack.getType() !== 'theme'),
80 | 'activateTime'
81 | )
82 | this.refs.packageActivationPanel.addPackages(packages, 'activateTime')
83 | this.refs.packageActivationPanel.refs.summary.textContent = dedent`
84 | Activated ${count} packages in ${time}ms.
85 | ${_.pluralize(packages.length, 'package')} took longer than 5ms to activate.\
86 | `
87 | }
88 |
89 | showLoadedThemes () {
90 | const {time, count, packages} = this.getSlowPackages(atom.themes.getLoadedThemes(), 'loadTime')
91 | this.refs.themeLoadingPanel.addPackages(packages, 'loadTime')
92 | this.refs.themeLoadingPanel.refs.summary.textContent = dedent`
93 | Loaded ${count} themes in ${time}ms.
94 | ${_.pluralize(packages.length, 'theme')} took longer than 5ms to load.\
95 | `
96 | }
97 |
98 | showActiveThemes () {
99 | const {time, count, packages} = this.getSlowPackages(atom.themes.getActiveThemes(), 'activateTime')
100 | this.refs.themeActivationPanel.addPackages(packages, 'activateTime')
101 | this.refs.themeActivationPanel.refs.summary.textContent = dedent`
102 | Activated ${count} themes in ${time}ms.
103 | ${_.pluralize(packages.length, 'theme')} took longer than 5ms to activate.\
104 | `
105 | }
106 |
107 | getSlowPackages (packages, timeKey) {
108 | let time = 0
109 | let count = 0
110 | packages = packages.filter(function (pack) {
111 | time += pack[timeKey]
112 | count++
113 | return pack[timeKey] > 5
114 | })
115 | packages.sort((pack1, pack2) => pack2[timeKey] - pack1[timeKey])
116 | return {time, count, packages}
117 | }
118 |
119 | serialize () {
120 | return {
121 | deserializer: this.constructor.name,
122 | uri: this.getURI()
123 | }
124 | }
125 |
126 | getURI () {
127 | return this.uri
128 | }
129 |
130 | getTitle () {
131 | return 'Timecop'
132 | }
133 |
134 | getIconName () {
135 | return 'dashboard'
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/lib/window-panel-view.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 | /** @jsx etch.dom */
3 |
4 | import {CompositeDisposable} from 'atom'
5 | import etch from 'etch'
6 |
7 | export default class WindowPanelView {
8 | constructor () {
9 | etch.initialize(this)
10 |
11 | this.disposables = new CompositeDisposable()
12 | this.disposables.add(atom.tooltips.add(this.refs.shellTiming, {title: 'The time taken to launch the app'}))
13 | this.disposables.add(atom.tooltips.add(this.refs.windowTiming, {title: 'The time taken to load this window'}))
14 | this.disposables.add(atom.tooltips.add(this.refs.projectTiming, {title: 'The time taken to rebuild the previously opened buffers'}))
15 | this.disposables.add(atom.tooltips.add(this.refs.workspaceTiming, {title: 'The time taken to rebuild the previously opened editors'}))
16 | }
17 |
18 | update () {}
19 |
20 | destroy () {
21 | this.disposables.dispose()
22 | return etch.destroy(this)
23 | }
24 |
25 | render () {
26 | return (
27 |
28 |
29 |
Startup Time
30 |
31 |
32 | Shell load time
33 | Loading…
34 |
35 |
36 |
37 | Window load time
38 | Loading…
39 |
40 |
41 |
42 |
43 | Project load time
44 | Loading…
45 |
46 |
47 |
48 | Workspace load time
49 | Loading…
50 |
51 |
52 |
53 |
54 |
55 | )
56 | }
57 |
58 | populate () {
59 | const time = atom.getWindowLoadTime()
60 | this.refs.windowLoadTime.classList.add(this.getHighlightClass(time))
61 | this.refs.windowLoadTime.textContent = `${time}ms`
62 |
63 | const {shellLoadTime} = atom.getLoadSettings()
64 | if (shellLoadTime != null) {
65 | this.refs.shellLoadTime.classList.add(this.getHighlightClass(shellLoadTime))
66 | this.refs.shellLoadTime.textContent = `${shellLoadTime}ms`
67 | } else {
68 | this.refs.shellTiming.style.display = 'none'
69 | }
70 |
71 | if (atom.deserializeTimings.project != null) {
72 | // Project and workspace timings only exist if the current project was previously opened
73 | this.refs.projectLoadTime.classList.add(this.getHighlightClass(atom.deserializeTimings.project))
74 | this.refs.projectLoadTime.textContent = `${atom.deserializeTimings.project}ms`
75 | this.refs.workspaceLoadTime.classList.add(this.getHighlightClass(atom.deserializeTimings.workspace))
76 | this.refs.workspaceLoadTime.textContent = `${atom.deserializeTimings.workspace}ms`
77 | } else {
78 | this.refs.deserializeTimings.style.display = 'none'
79 | }
80 | }
81 |
82 | getHighlightClass (time) {
83 | if (time > 1000) {
84 | return 'highlight-error'
85 | } else if (time > 800) {
86 | return 'highlight-warning'
87 | } else {
88 | return 'highlight-info'
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/menus/timecop.cson:
--------------------------------------------------------------------------------
1 | 'menu': [
2 | 'label': 'Packages'
3 | 'submenu': [
4 | 'label': 'Timecop'
5 | 'submenu': [
6 | 'label': 'Show'
7 | 'command': 'timecop:view'
8 | ]
9 | ]
10 | ]
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "timecop",
3 | "version": "0.36.2",
4 | "description": "Displays information about where time is spent while Atom loads.",
5 | "main": "./lib/main",
6 | "repository": "https://github.com/atom/timecop",
7 | "license": "MIT",
8 | "engines": {
9 | "atom": "*"
10 | },
11 | "deserializers": {
12 | "TimecopView": "createTimecopView"
13 | },
14 | "dependencies": {
15 | "dedent": "^0.7.0",
16 | "etch": "^0.12.6",
17 | "underscore-plus": "^1.0.0"
18 | },
19 | "devDependencies": {
20 | "standard": "^10.0.3"
21 | },
22 | "standard": {
23 | "env": {
24 | "atomtest": true,
25 | "browser": true,
26 | "jasmine": true,
27 | "node": true
28 | },
29 | "globals": [
30 | "atom"
31 | ]
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/spec/async-spec-helpers.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | export function beforeEach (fn) {
4 | global.beforeEach(function () {
5 | const result = fn()
6 | if (result instanceof Promise) {
7 | waitsForPromise(() => result)
8 | }
9 | })
10 | }
11 |
12 | export function afterEach (fn) {
13 | global.afterEach(function () {
14 | const result = fn()
15 | if (result instanceof Promise) {
16 | waitsForPromise(() => result)
17 | }
18 | })
19 | }
20 |
21 | ['it', 'fit', 'ffit', 'fffit'].forEach(function (name) {
22 | module.exports[name] = function (description, fn) {
23 | if (fn === undefined) {
24 | global[name](description)
25 | return
26 | }
27 |
28 | global[name](description, function () {
29 | const result = fn()
30 | if (result instanceof Promise) {
31 | waitsForPromise(() => result)
32 | }
33 | })
34 | }
35 | })
36 |
37 | export async function conditionPromise (condition, description = 'anonymous condition') {
38 | const startTime = Date.now()
39 |
40 | while (true) {
41 | await timeoutPromise(100)
42 |
43 | if (await condition()) {
44 | return
45 | }
46 |
47 | if (Date.now() - startTime > 5000) {
48 | throw new Error('Timed out waiting on ' + description)
49 | }
50 | }
51 | }
52 |
53 | export function timeoutPromise (timeout) {
54 | return new Promise(function (resolve) {
55 | global.setTimeout(resolve, timeout)
56 | })
57 | }
58 |
59 | function waitsForPromise (fn) {
60 | const promise = fn()
61 | global.waitsFor('spec promise to resolve', function (done) {
62 | promise.then(done, function (error) {
63 | jasmine.getEnv().currentSpec.fail(error)
64 | done()
65 | })
66 | })
67 | }
68 |
69 | export function emitterEventPromise (emitter, event, timeout = 15000) {
70 | return new Promise((resolve, reject) => {
71 | const timeoutHandle = setTimeout(() => {
72 | reject(new Error(`Timed out waiting for '${event}' event`))
73 | }, timeout)
74 | emitter.once(event, () => {
75 | clearTimeout(timeoutHandle)
76 | resolve()
77 | })
78 | })
79 | }
80 |
81 | export function promisify (original) {
82 | return function (...args) {
83 | return new Promise((resolve, reject) => {
84 | args.push((err, ...results) => {
85 | if (err) {
86 | reject(err)
87 | } else {
88 | resolve(...results)
89 | }
90 | })
91 |
92 | return original(...args)
93 | })
94 | }
95 | }
96 |
97 | export function promisifySome (obj, fnNames) {
98 | const result = {}
99 | for (const fnName of fnNames) {
100 | result[fnName] = promisify(obj[fnName])
101 | }
102 | return result
103 | }
104 |
--------------------------------------------------------------------------------
/spec/timecop-spec.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const CompileCache = require(path.join(atom.getLoadSettings().resourcePath, 'src', 'compile-cache'))
3 | const CSON = require(path.join(atom.getLoadSettings().resourcePath, 'node_modules', 'season'))
4 |
5 | const {it, fit, ffit, beforeEach, afterEach} = require('./async-spec-helpers') // eslint-disable-line no-unused-vars
6 |
7 | describe('Timecop', () => {
8 | beforeEach(async () => {
9 | spyOn(CompileCache, 'getCacheStats').andReturn({
10 | '.js': {hits: 3, misses: 4},
11 | '.ts': {hits: 5, misses: 6},
12 | '.coffee': {hits: 7, misses: 8}
13 | })
14 |
15 | spyOn(CSON, 'getCacheMisses').andReturn(10)
16 |
17 | atom.themes.lessCache.cache.stats.misses = 12
18 |
19 | await atom.packages.activatePackage('timecop')
20 | })
21 |
22 | describe('the Timecop view', () => {
23 | let timecopView = null
24 |
25 | beforeEach(async () => {
26 | const packages = [
27 | new FakePackage({
28 | name: 'slow-activating-package-1',
29 | activateTime: 500,
30 | loadTime: 5
31 | }),
32 | new FakePackage({
33 | name: 'slow-activating-package-2',
34 | activateTime: 500,
35 | loadTime: 5
36 | }),
37 | new FakePackage({
38 | name: 'slow-loading-package',
39 | activateTime: 5,
40 | loadTime: 500
41 | }),
42 | new FakePackage({
43 | name: 'fast-package',
44 | activateTime: 2,
45 | loadTime: 3
46 | })
47 | ]
48 |
49 | spyOn(atom.packages, 'getLoadedPackages').andReturn(packages)
50 | spyOn(atom.packages, 'getActivePackages').andReturn(packages)
51 | spyOn(atom.packages, 'hasLoadedInitialPackages').andReturn(true)
52 | spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true)
53 |
54 | timecopView = await atom.workspace.open('atom://timecop')
55 | })
56 |
57 | afterEach(() => jasmine.unspy(atom.packages, 'getLoadedPackages'))
58 |
59 | it('shows the packages that loaded slowly', () => {
60 | const loadingPanel = timecopView.refs.packageLoadingPanel
61 | expect(loadingPanel.element.textContent).toMatch(/1 package took longer than 5ms to load/)
62 | expect(loadingPanel.element.textContent).toMatch(/slow-loading-package/)
63 |
64 | expect(loadingPanel.element.textContent).not.toMatch(/slow-activating-package/)
65 | expect(loadingPanel.element.textContent).not.toMatch(/fast-package/)
66 | })
67 |
68 | it('shows the packages that activated slowly', () => {
69 | const activationPanel = timecopView.refs.packageActivationPanel
70 | expect(activationPanel.element.textContent).toMatch(/2 packages took longer than 5ms to activate/)
71 | expect(activationPanel.element.textContent).toMatch(/slow-activating-package-1/)
72 | expect(activationPanel.element.textContent).toMatch(/slow-activating-package-2/)
73 |
74 | expect(activationPanel.element.textContent).not.toMatch(/slow-loading-package/)
75 | expect(activationPanel.element.textContent).not.toMatch(/fast-package/)
76 | })
77 |
78 | it('shows how many files were transpiled from each language', () => {
79 | const cachePanel = timecopView.refs.cacheLoadingPanel
80 |
81 | expect(cachePanel.element.textContent).toMatch(/CoffeeScript files compiled\s*8/)
82 | expect(cachePanel.element.textContent).toMatch(/Babel files compiled\s*4/)
83 | expect(cachePanel.element.textContent).toMatch(/Typescript files compiled\s*6/)
84 | expect(cachePanel.element.textContent).toMatch(/CSON files compiled\s*10/)
85 | expect(cachePanel.element.textContent).toMatch(/Less files compiled\s*12/)
86 | })
87 | })
88 | })
89 |
90 | class FakePackage {
91 | constructor ({name, activateTime, loadTime}) {
92 | this.name = name
93 | this.activateTime = activateTime
94 | this.loadTime = loadTime
95 | }
96 | getType () { return 'package' }
97 | isTheme () { return false }
98 | }
99 |
--------------------------------------------------------------------------------
/styles/timecop.less:
--------------------------------------------------------------------------------
1 | @import "ui-variables";
2 |
3 | .timecop {
4 | .timecop-panel {
5 | background-color: @tool-panel-background-color;
6 | }
7 |
8 | overflow: auto;
9 |
10 | .panels {
11 | display: flex;
12 | flex-direction: row;
13 | }
14 |
15 | .package-panel {
16 | display: flex;
17 | flex-direction: row;
18 | flex: 1;
19 |
20 | .list-group {
21 | overflow: hidden;
22 | }
23 |
24 | .inset-panel {
25 | width: 100%;
26 | }
27 | }
28 |
29 | .text-info {
30 | margin-bottom: @component-padding;
31 | }
32 |
33 | .timing,
34 | .list-item {
35 | display: flex;
36 | justify-content: space-between;
37 | .inline-block:last-child {
38 | margin-right: 0;
39 | }
40 | }
41 |
42 | .timing {
43 | margin-bottom: @component-padding;
44 | }
45 |
46 | .list-item {
47 | margin-top: @component-padding / 2;;
48 | margin-bottom: @component-padding / 2;
49 | }
50 |
51 | .package {
52 | color: @text-color;
53 | &:hover {
54 | text-decoration: none;
55 | color: @text-color-selected;
56 | }
57 | }
58 | .timecop-line {
59 | flex: 1 1 0;
60 | margin-right: @component-padding;
61 | margin-top: .80em;
62 | border-top: 1px dashed fade(@text-color, 20%);
63 | }
64 |
65 | .package:hover + .timecop-line {
66 | border-color: fade(@text-color, 60%);
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------