├── .gitignore ├── .coffeelintignore ├── CONTRIBUTING.md ├── spec ├── fixtures │ ├── sample.js │ └── sample.coffee ├── async-spec-helpers.js └── autosave-spec.js ├── .github ├── workflows │ └── ci.yml └── no-response.yml ├── lib ├── controls.js └── autosave.js ├── coffeelint.json ├── LICENSE.md ├── package.json ├── PULL_REQUEST_TEMPLATE.md ├── README.md └── ISSUE_TEMPLATE.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.coffeelintignore: -------------------------------------------------------------------------------- 1 | spec/fixtures 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | See the [Atom contributing guide](https://atom.io/docs/latest/contributing) 2 | -------------------------------------------------------------------------------- /spec/fixtures/sample.js: -------------------------------------------------------------------------------- 1 | var quicksort = function () { 2 | var sort = function(items) { 3 | if (items.length <= 1) return items; 4 | var pivot = items.shift(), current, left = [], right = []; 5 | while(items.length > 0) { 6 | current = items.shift(); 7 | current < pivot ? left.push(current) : right.push(current); 8 | } 9 | return sort(left).concat(pivot).concat(sort(right)); 10 | }; 11 | 12 | return sort(Array.apply(this, arguments)); 13 | }; -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /lib/controls.js: -------------------------------------------------------------------------------- 1 | const tests = [] 2 | 3 | module.exports = { 4 | // Public: Add a predicate to set of tests 5 | // 6 | // * `predicate` A {Function} determining if a {PaneItem} should autosave. 7 | // 8 | // Returns `undefined`. 9 | dontSaveIf (predicate) { 10 | tests.push(predicate) 11 | }, 12 | 13 | // Public: Test whether a paneItem should be autosaved. 14 | // 15 | // * `paneItem` A pane item {Object}. 16 | // 17 | // Returns `Boolean`. 18 | shouldSave (paneItem) { 19 | return !tests.some(test => test(paneItem)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spec/fixtures/sample.coffee: -------------------------------------------------------------------------------- 1 | class Quicksort 2 | sort: (items) -> 3 | return items if items.length <= 1 4 | 5 | pivot = items.shift() 6 | left = [] 7 | right = [] 8 | 9 | # Comment in the middle (and add the word 'items' again) 10 | 11 | while items.length > 0 12 | current = items.shift() 13 | if current < pivot 14 | left.push(current) 15 | else 16 | right.push(current) 17 | 18 | sort(left).concat(pivot).concat(sort(right)) 19 | 20 | noop: -> 21 | # just a noop 22 | 23 | exports.modules = quicksort 24 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /coffeelint.json: -------------------------------------------------------------------------------- 1 | { 2 | "max_line_length": { 3 | "level": "ignore" 4 | }, 5 | "no_empty_param_list": { 6 | "level": "error" 7 | }, 8 | "arrow_spacing": { 9 | "level": "error" 10 | }, 11 | "no_interpolation_in_single_quotes": { 12 | "level": "error" 13 | }, 14 | "no_debugger": { 15 | "level": "error" 16 | }, 17 | "prefer_english_operator": { 18 | "level": "error" 19 | }, 20 | "colon_assignment_spacing": { 21 | "spacing": { 22 | "left": 0, 23 | "right": 1 24 | }, 25 | "level": "error" 26 | }, 27 | "braces_spacing": { 28 | "spaces": 0, 29 | "level": "error" 30 | }, 31 | "spacing_after_comma": { 32 | "level": "error" 33 | }, 34 | "no_stand_alone_at": { 35 | "level": "error" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "autosave", 3 | "main": "./lib/autosave", 4 | "version": "0.24.6", 5 | "private": true, 6 | "description": "Save editors when they lose focus or are closed", 7 | "repository": "https://github.com/atom/autosave", 8 | "engines": { 9 | "atom": ">0.27.0" 10 | }, 11 | "providedServices": { 12 | "autosave": { 13 | "description": "A configuration object to control what is autosaved", 14 | "versions": { 15 | "1.0.0": "provideService" 16 | } 17 | } 18 | }, 19 | "dependencies": { 20 | "fs-plus": "^3.0.0" 21 | }, 22 | "devDependencies": { 23 | "standard": "^10.0.3" 24 | }, 25 | "standard": { 26 | "ignore": [ 27 | "spec/fixtures" 28 | ], 29 | "globals": [ 30 | "atom", 31 | "describe", 32 | "expect", 33 | "beforeEach", 34 | "jasmine", 35 | "waitsForPromise", 36 | "waitsFor", 37 | "runs", 38 | "spyOn", 39 | "FocusEvent", 40 | "it" 41 | ] 42 | }, 43 | "configSchema": { 44 | "enabled": { 45 | "type": "boolean", 46 | "default": false 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /spec/async-spec-helpers.js: -------------------------------------------------------------------------------- 1 | exports.beforeEach = function beforeEach (fn) { 2 | global.beforeEach(function () { 3 | const result = fn() 4 | if (result instanceof Promise) { 5 | waitsForPromise(() => result) 6 | } 7 | }) 8 | } 9 | 10 | exports.afterEach = function afterEach (fn) { 11 | global.afterEach(function () { 12 | const result = fn() 13 | if (result instanceof Promise) { 14 | waitsForPromise(() => result) 15 | } 16 | }) 17 | } 18 | 19 | ;['it', 'fit', 'ffit', 'fffit'].forEach(function (name) { 20 | exports[name] = function (description, fn) { 21 | if (fn === undefined) { 22 | global[name](description) 23 | return 24 | } 25 | 26 | global[name](description, function () { 27 | const result = fn() 28 | if (result instanceof Promise) { 29 | waitsForPromise(() => result) 30 | } 31 | }) 32 | } 33 | }) 34 | 35 | function waitsForPromise (fn) { 36 | const promise = fn() 37 | global.waitsFor('spec promise to resolve', function (done) { 38 | promise.then(done, function (error) { 39 | jasmine.getEnv().currentSpec.fail(error) 40 | done() 41 | }) 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /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 | # Autosave package 3 | [![OS X Build Status](https://travis-ci.org/atom/autosave.svg?branch=master)](https://travis-ci.org/atom/autosave) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/3aktr9updp722fqx/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/autosave/branch/master) [![Dependency Status](https://david-dm.org/atom/autosave.svg)](https://david-dm.org/atom/autosave) 4 | 5 | Autosaves editor when they lose focus, are destroyed, or when the window is closed. 6 | 7 | This package is disabled by default and can be enabled via the `autosave.enabled` config 8 | setting or by checking *Enabled* in the settings for the *autosave* package in the 9 | Settings view. 10 | 11 | ## Service API 12 | The service exposes an object with a function `dontSaveIf`, which accepts a callback. 13 | Callbacks will be invoked with each pane item eligible for an autosave and if the callback 14 | returns true, the item will be skipped. 15 | 16 | ### Usage 17 | 18 | #### package.json 19 | ``` json 20 | "consumedServices": { 21 | "autosave": { 22 | "versions": { 23 | "1.0.0": "consumeAutosave" 24 | } 25 | } 26 | } 27 | ``` 28 | 29 | #### package initialize 30 | ``` javascript 31 | consumeAutosave({dontSaveIf}) { 32 | dontSaveIf(paneItem -> paneItem.getPath() === '/dont/autosave/me.coffee') 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/autosave.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-plus') 2 | const {CompositeDisposable, Disposable} = require('atom') 3 | const {dontSaveIf, shouldSave} = require('./controls') 4 | 5 | module.exports = { 6 | subscriptions: null, 7 | 8 | provideService () { 9 | return {dontSaveIf} 10 | }, 11 | 12 | activate () { 13 | this.subscriptions = new CompositeDisposable() 14 | 15 | const handleBlur = event => { 16 | if (event.target === window) { 17 | this.autosaveAllPaneItems() 18 | } else if (event.target.matches('atom-text-editor:not(mini)')) { 19 | return this.autosavePaneItem(event.target.getModel()) 20 | } 21 | } 22 | 23 | window.addEventListener('blur', handleBlur, true) 24 | this.subscriptions.add(new Disposable(() => window.removeEventListener('blur', handleBlur, true))) 25 | 26 | this.subscriptions.add(atom.workspace.onDidAddPaneItem(({item}) => this.autosavePaneItem(item, true))) 27 | this.subscriptions.add(atom.workspace.onWillDestroyPaneItem(({item}) => this.autosavePaneItem(item))) 28 | }, 29 | 30 | deactivate () { 31 | this.subscriptions.dispose() 32 | return this.autosaveAllPaneItems() 33 | }, 34 | 35 | autosavePaneItem (paneItem, create = false) { 36 | if (!atom.config.get('autosave.enabled')) return 37 | if (!paneItem) return 38 | if (typeof paneItem.getURI !== 'function' || !paneItem.getURI()) return 39 | if (typeof paneItem.isModified !== 'function' || !paneItem.isModified()) return 40 | if (typeof paneItem.getPath !== 'function' || !paneItem.getPath()) return 41 | if (!shouldSave(paneItem)) return 42 | 43 | try { 44 | const stats = fs.statSync(paneItem.getPath()) 45 | if (!stats.isFile()) return 46 | } catch (e) { 47 | if (e.code !== 'ENOENT') return 48 | if (!create) return 49 | } 50 | 51 | const pane = atom.workspace.paneForItem(paneItem) 52 | let promise = Promise.resolve() 53 | if (pane) { 54 | promise = pane.saveItem(paneItem) 55 | } else if (typeof paneItem.save === 'function') { 56 | promise = paneItem.save() 57 | } 58 | return promise 59 | }, 60 | 61 | autosaveAllPaneItems () { 62 | return Promise.all( 63 | atom.workspace.getPaneItems().map((paneItem) => this.autosavePaneItem(paneItem)) 64 | ) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /spec/autosave-spec.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-plus') 2 | const {it, fit, ffit, beforeEach} = require('./async-spec-helpers') // eslint-disable-line 3 | 4 | describe('Autosave', () => { 5 | let workspaceElement, initialActiveItem, otherItem1, otherItem2 6 | 7 | beforeEach(async () => { 8 | workspaceElement = atom.views.getView(atom.workspace) 9 | jasmine.attachToDOM(workspaceElement) 10 | 11 | await atom.packages.activatePackage('autosave') 12 | 13 | await atom.workspace.open('sample.js') 14 | 15 | initialActiveItem = atom.workspace.getActiveTextEditor() 16 | 17 | if (atom.workspace.createItemForURI != null) { 18 | otherItem1 = await atom.workspace.createItemForURI('sample.coffee') 19 | } else { 20 | otherItem1 = await atom.workspace.open('sample.coffee', {activateItem: false}) 21 | } 22 | 23 | otherItem2 = otherItem1.copy() 24 | 25 | spyOn(initialActiveItem, 'save').andCallFake(() => Promise.resolve()) 26 | spyOn(otherItem1, 'save').andCallFake(() => Promise.resolve()) 27 | spyOn(otherItem2, 'save').andCallFake(() => Promise.resolve()) 28 | }) 29 | 30 | describe('when the item is not modified', () => { 31 | it('does not autosave the item', () => { 32 | atom.config.set('autosave.enabled', true) 33 | atom.workspace.getActivePane().splitRight({items: [otherItem1]}) 34 | expect(initialActiveItem.save).not.toHaveBeenCalled() 35 | }) 36 | }) 37 | 38 | describe('when the buffer is modified', () => { 39 | beforeEach(() => initialActiveItem.setText('i am modified')) 40 | 41 | it('autosaves newly added items', async () => { 42 | const newItem = await atom.workspace.createItemForURI('notyet.js') 43 | spyOn(newItem, 'isModified').andReturn(true) 44 | 45 | atom.config.set('autosave.enabled', true) 46 | spyOn(atom.workspace.getActivePane(), 'saveItem').andCallFake(() => Promise.resolve()) 47 | atom.workspace.getActivePane().addItem(newItem) 48 | 49 | expect(atom.workspace.getActivePane().saveItem).toHaveBeenCalledWith(newItem) 50 | }) 51 | 52 | describe('when a pane loses focus', () => { 53 | it('saves the item if autosave is enabled and the item has a uri', () => { 54 | document.body.focus() 55 | expect(initialActiveItem.save).not.toHaveBeenCalled() 56 | 57 | workspaceElement.focus() 58 | atom.config.set('autosave.enabled', true) 59 | document.body.focus() 60 | expect(initialActiveItem.save).toHaveBeenCalled() 61 | }) 62 | 63 | it('suppresses autosave if the file does not exist', () => { 64 | document.body.focus() 65 | expect(initialActiveItem.save).not.toHaveBeenCalled() 66 | 67 | workspaceElement.focus() 68 | atom.config.set('autosave.enabled', true) 69 | 70 | const originalPath = atom.workspace.getActiveTextEditor().getPath() 71 | const tmpPath = `${originalPath}~` 72 | fs.renameSync(originalPath, tmpPath) 73 | 74 | document.body.focus() 75 | expect(initialActiveItem.save).not.toHaveBeenCalled() 76 | 77 | fs.renameSync(tmpPath, originalPath) 78 | }) 79 | 80 | it('suppresses autosave if the focused element is contained by the editor, such as occurs when opening the autocomplete menu', () => { 81 | atom.config.set('autosave.enabled', true) 82 | const focusStealer = document.createElement('div') 83 | focusStealer.setAttribute('tabindex', -1) 84 | 85 | const textEditorElement = atom.views.getView(atom.workspace.getActiveTextEditor()) 86 | textEditorElement.appendChild(focusStealer) 87 | focusStealer.focus() 88 | expect(initialActiveItem.save).not.toHaveBeenCalled() 89 | }) 90 | }) 91 | 92 | describe('when a new pane is created', () => { 93 | it('saves the item if autosave is enabled and the item has a uri', () => { 94 | const leftPane = atom.workspace.getActivePane() 95 | const rightPane = leftPane.splitRight() 96 | expect(initialActiveItem.save).not.toHaveBeenCalled() 97 | 98 | rightPane.destroy() 99 | leftPane.activate() 100 | 101 | atom.config.set('autosave.enabled', true) 102 | leftPane.splitRight() 103 | expect(initialActiveItem.save).toHaveBeenCalled() 104 | }) 105 | }) 106 | 107 | describe('when an item is destroyed', () => { 108 | describe('when the item is the active item', () => { 109 | it('does not save the item if autosave is enabled and the item has a uri', async () => { 110 | let leftPane = atom.workspace.getActivePane() 111 | const rightPane = leftPane.splitRight({items: [otherItem1]}) 112 | leftPane.activate() 113 | expect(initialActiveItem).toBe(atom.workspace.getActivePaneItem()) 114 | leftPane.destroyItem(initialActiveItem) 115 | expect(initialActiveItem.save).not.toHaveBeenCalled() 116 | 117 | otherItem2.setText('I am also modified') 118 | atom.config.set('autosave.enabled', true) 119 | leftPane = rightPane.splitLeft({items: [otherItem2]}) 120 | expect(otherItem2).toBe(atom.workspace.getActivePaneItem()) 121 | await leftPane.destroyItem(otherItem2) 122 | expect(otherItem2.save).toHaveBeenCalled() 123 | }) 124 | }) 125 | 126 | describe('when the item is NOT the active item', () => { 127 | it('does not save the item if autosave is enabled and the item has a uri', () => { 128 | let leftPane = atom.workspace.getActivePane() 129 | const rightPane = leftPane.splitRight({items: [otherItem1]}) 130 | expect(initialActiveItem).not.toBe(atom.workspace.getActivePaneItem()) 131 | leftPane.destroyItem(initialActiveItem) 132 | expect(initialActiveItem.save).not.toHaveBeenCalled() 133 | 134 | otherItem2.setText('I am also modified') 135 | atom.config.set('autosave.enabled', true) 136 | leftPane = rightPane.splitLeft({items: [otherItem2]}) 137 | rightPane.focus() 138 | expect(otherItem2).not.toBe(atom.workspace.getActivePaneItem()) 139 | leftPane.destroyItem(otherItem2) 140 | expect(otherItem2.save).toHaveBeenCalled() 141 | }) 142 | }) 143 | }) 144 | 145 | describe('when the item does not have a URI', () => { 146 | it('does not save the item', async () => { 147 | await atom.workspace.open() 148 | 149 | const pathLessItem = atom.workspace.getActiveTextEditor() 150 | spyOn(pathLessItem, 'save').andCallThrough() 151 | pathLessItem.setText('text!') 152 | expect(pathLessItem.getURI()).toBeFalsy() 153 | 154 | atom.config.set('autosave.enabled', true) 155 | atom.workspace.getActivePane().destroyItem(pathLessItem) 156 | expect(pathLessItem.save).not.toHaveBeenCalled() 157 | }) 158 | }) 159 | }) 160 | 161 | describe('when the window is blurred', () => { 162 | it('saves all items', () => { 163 | atom.config.set('autosave.enabled', true) 164 | 165 | const leftPane = atom.workspace.getActivePane() 166 | leftPane.splitRight({items: [otherItem1]}) 167 | 168 | initialActiveItem.insertText('a') 169 | otherItem1.insertText('b') 170 | 171 | window.dispatchEvent(new FocusEvent('blur')) 172 | 173 | expect(initialActiveItem.save).toHaveBeenCalled() 174 | expect(otherItem1.save).toHaveBeenCalled() 175 | }) 176 | }) 177 | 178 | describe('when the package is deactivated', () => { 179 | it('saves all items and waits for saves to complete', () => { 180 | atom.config.set('autosave.enabled', true) 181 | 182 | const leftPane = atom.workspace.getActivePane() 183 | leftPane.splitRight({items: [otherItem1]}) 184 | 185 | initialActiveItem.insertText('a') 186 | otherItem1.insertText('b') 187 | 188 | let deactivated = false 189 | let asyncDeactivateSupported = true 190 | let resolveInitial = () => {} 191 | let resolveOther = () => {} 192 | initialActiveItem.save.andCallFake(() => { 193 | return new Promise(resolve => { 194 | resolveInitial = resolve 195 | }) 196 | }) 197 | otherItem1.save.andCallFake(() => { 198 | return new Promise(resolve => { 199 | resolveOther = resolve 200 | }) 201 | }) 202 | 203 | let deactivatePromise = atom.packages.deactivatePackage('autosave') 204 | if (!deactivatePromise || !deactivatePromise.then || typeof deactivatePromise.then !== 'function') { 205 | // Atom does not support asynchronous package deactivation. 206 | // This keeps us from failing on 1.20 207 | asyncDeactivateSupported = false 208 | deactivatePromise = Promise.resolve() 209 | } 210 | deactivatePromise.then((result) => { 211 | if (result === undefined) { 212 | // This keeps us from failing on 1.21-beta1 213 | asyncDeactivateSupported = false 214 | } 215 | deactivated = true 216 | }) 217 | 218 | waitsForPromise(() => Promise.resolve()) 219 | 220 | runs(() => { 221 | if (asyncDeactivateSupported) { 222 | expect(deactivated).toBe(false) 223 | } 224 | 225 | resolveInitial() 226 | resolveOther() 227 | }) 228 | 229 | waitsFor(() => !asyncDeactivateSupported || deactivated) 230 | }) 231 | }) 232 | 233 | it("saves via the item's Pane so that write errors are handled via notifications", async () => { 234 | const saveError = new Error('Save failed') 235 | saveError.code = 'EACCES' 236 | saveError.path = initialActiveItem.getPath() 237 | initialActiveItem.save.andThrow(saveError) 238 | 239 | const errorCallback = jasmine.createSpy('errorCallback').andCallFake(({preventDefault}) => preventDefault()) 240 | atom.onWillThrowError(errorCallback) 241 | spyOn(atom.notifications, 'addWarning') 242 | 243 | initialActiveItem.insertText('a') 244 | atom.config.set('autosave.enabled', true) 245 | 246 | await atom.workspace.destroyActivePaneItem() 247 | expect(initialActiveItem.save).toHaveBeenCalled() 248 | expect(atom.notifications.addWarning.callCount > 0 || errorCallback.callCount > 0).toBe(true) 249 | }) 250 | 251 | describe('dontSaveIf service', () => { 252 | it("doesn't save a paneItem if a predicate function registered via the dontSaveIf service returns true", async () => { 253 | atom.workspace.getActivePane().addItem(otherItem1) 254 | atom.config.set('autosave.enabled', true) 255 | const service = atom.packages.getActivePackage('autosave').mainModule.provideService() 256 | service.dontSaveIf(paneItem => paneItem === initialActiveItem) 257 | 258 | initialActiveItem.setText('foo') 259 | otherItem1.setText('bar') 260 | 261 | window.dispatchEvent(new FocusEvent('blur')) 262 | 263 | expect(initialActiveItem.save).not.toHaveBeenCalled() 264 | expect(otherItem1.save).toHaveBeenCalled() 265 | }) 266 | }) 267 | }) 268 | --------------------------------------------------------------------------------